pax_global_header 0000666 0000000 0000000 00000000064 13620024551 0014510 g ustar 00root root 0000000 0000000 52 comment=61318e6ed6be71ddb5458d17d9d874ad9314c97e
node-redis-3.0.2/ 0000775 0000000 0000000 00000000000 13620024551 0013543 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/.eslintignore 0000664 0000000 0000000 00000000051 13620024551 0016242 0 ustar 00root root 0000000 0000000 node_modules/**
coverage/**
**.md
**.log
node-redis-3.0.2/.eslintrc 0000664 0000000 0000000 00000004420 13620024551 0015367 0 ustar 00root root 0000000 0000000 env:
node: true
es6: false
rules:
# Possible Errors
# http://eslint.org/docs/rules/#possible-errors
comma-dangle: [2, "only-multiline"]
no-constant-condition: 2
no-control-regex: 2
no-debugger: 2
no-dupe-args: 2
no-dupe-keys: 2
no-duplicate-case: 2
no-empty: 2
no-empty-character-class: 2
no-ex-assign: 2
no-extra-boolean-cast : 2
no-extra-parens: [2, "functions"]
no-extra-semi: 2
no-func-assign: 2
no-invalid-regexp: 2
no-irregular-whitespace: 2
no-negated-in-lhs: 2
no-obj-calls: 2
no-regex-spaces: 2
no-sparse-arrays: 2
no-inner-declarations: 2
no-unexpected-multiline: 2
no-unreachable: 2
use-isnan: 2
valid-typeof: 2
# Best Practices
# http://eslint.org/docs/rules/#best-practices
array-callback-return: 2
block-scoped-var: 2
dot-notation: 2
eqeqeq: 2
no-else-return: 2
no-extend-native: 2
no-floating-decimal: 2
no-extra-bind: 2
no-fallthrough: 2
no-labels: 2
no-lone-blocks: 2
no-loop-func: 2
no-multi-spaces: 2
no-multi-str: 2
no-native-reassign: 2
no-new-wrappers: 2
no-octal: 2
no-proto: 2
no-redeclare: 2
no-return-assign: 2
no-self-assign: 2
no-self-compare: 2
no-sequences: 2
no-throw-literal: 2
no-useless-call: 2
no-useless-concat: 2
no-useless-escape: 2
no-void: 2
no-unmodified-loop-condition: 2
yoda: 2
# Strict Mode
# http://eslint.org/docs/rules/#strict-mode
strict: [2, "global"]
# Variables
# http://eslint.org/docs/rules/#variables
no-delete-var: 2
no-shadow-restricted-names: 2
no-undef: 2
no-unused-vars: [2, {"args": "none"}]
# http://eslint.org/docs/rules/#nodejs-and-commonjs
no-mixed-requires: 2
no-new-require: 2
no-path-concat: 2
# Stylistic Issues
# http://eslint.org/docs/rules/#stylistic-issues
comma-spacing: 2
eol-last: 2
indent: [2, 4, {SwitchCase: 2}]
keyword-spacing: 2
max-len: [2, 200, 2]
new-parens: 2
no-mixed-spaces-and-tabs: 2
no-multiple-empty-lines: [2, {max: 2}]
no-trailing-spaces: 2
quotes: [2, "single", "avoid-escape"]
semi: 2
space-before-blocks: [2, "always"]
space-before-function-paren: [2, "always"]
space-in-parens: [2, "never"]
space-infix-ops: 2
space-unary-ops: 2
globals:
it: true
describe: true
before: true
after: true
beforeEach: true
afterEach: true
node-redis-3.0.2/.github/ 0000775 0000000 0000000 00000000000 13620024551 0015103 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/.github/FUNDING.yml 0000664 0000000 0000000 00000000034 13620024551 0016715 0 ustar 00root root 0000000 0000000 open_collective: node-redis
node-redis-3.0.2/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000001571 13620024551 0017614 0 ustar 00root root 0000000 0000000 ---
title: ⚠️ Bug report
labels: needs-triage
---
### Issue
> Describe your issue here
---
### Environment
- **Node.js Version**: `VERSION_HERE`
- **Redis Version**: `VERSION_HERE`
- **Platform**: `PLATFORM_HERE`
node-redis-3.0.2/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000001337 13620024551 0020710 0 ustar 00root root 0000000 0000000
### Description
> Description your pull request here
---
### Checklist
- [ ] Does `npm test` pass with this change (including linting)?
- [ ] Is the new or changed code fully tested?
- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
node-redis-3.0.2/.gitignore 0000664 0000000 0000000 00000000245 13620024551 0015534 0 ustar 00root root 0000000 0000000 node_modules
.tern-port
.nyc_output
coverage
*.log
*.rdb
stunnel.conf
stunnel.pid
*.out
package-lock.json
# IntelliJ IDEs
.idea
# VisualStudioCode IDEs
.vscode
.vs
node-redis-3.0.2/.npmignore 0000664 0000000 0000000 00000000337 13620024551 0015545 0 ustar 00root root 0000000 0000000 examples/
benchmarks/
test/
.nyc_output/
coverage/
.github/
.eslintignore
.eslintrc
.tern-port
*.log
*.rdb
*.out
*.yml
.vscode
.idea
CONTRIBUTING.md
CODE_OF_CONDUCT.md
.travis.yml
appveyor.yml
package-lock.json
.prettierrc
node-redis-3.0.2/.prettierrc 0000664 0000000 0000000 00000000322 13620024551 0015724 0 ustar 00root root 0000000 0000000 {
"arrowParens": "avoid",
"trailingComma": "all",
"useTabs": false,
"semi": true,
"singleQuote": false,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"tabWidth": 2,
"printWidth": 100
}
node-redis-3.0.2/.travis.yml 0000664 0000000 0000000 00000002245 13620024551 0015657 0 ustar 00root root 0000000 0000000 language: node_js
sudo: required
os:
- windows
- linux
node_js:
- "6"
- "8"
- "10"
- "12"
- "13"
before_install:
- |-
case $TRAVIS_OS_NAME in
linux)
if [[ ! -f stunnel.tar.gz ]]; then wget -O stunnel.tar.gz ftp://ftp.stunnel.org/stunnel/archive/5.x/stunnel-5.54.tar.gz; fi
if [[ ! -f ./stunnel-5.54/configure ]]; then tar -xzf stunnel.tar.gz; fi
if [[ ! -f ./stunnel-5.54/src/stunnel ]]; then cd ./stunnel-5.54; ./configure; make; cd ..; fi
export PATH="$PATH:$(pwd)/stunnel-5.54/src"
;;
esac
- |-
case $TRAVIS_OS_NAME in
windows)
choco install redis-64
redis-server --service-install
redis-server --service-start
redis-cli config set stop-writes-on-bgsave-error no
;;
esac
cache:
directories:
- "$HOME/AppData/Local/Temp/chocolatey"
- "$TRAVIS_BUILD_DIR/stunnel-5.54"
before_script:
# Add an IPv6 config - see the corresponding Travis issue
# https://github.com/travis-ci/travis-ci/issues/8361
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';
fi
after_success: npm run coveralls
node-redis-3.0.2/CHANGELOG.md 0000664 0000000 0000000 00000117011 13620024551 0015355 0 ustar 00root root 0000000 0000000 # Changelog
## v3.0.0 - 09 Feb, 2020
This version is mainly a release to distribute all the unreleased changes on master since 2017 and additionally removes
a lot of old deprecated features and old internals in preparation for an upcoming modernization refactor (v4).
### Breaking Changes
- Dropped support for Node.js < 6
- Dropped support for `hiredis` (no longer required)
- Removed previously deprecated `drain` event
- Removed previously deprecated `idle` event
- Removed previously deprecated `parser` option
- Removed previously deprecated `max_delay` option
- Removed previously deprecated `max_attempts` option
- Removed previously deprecated `socket_no_delay` option
### Bug Fixes
- Removed development files from published package (#1370)
- Duplicate function now allows db param to be passed (#1311)
### Features
- Upgraded to latest `redis-commands` package
- Upgraded to latest `redis-parser` package, v3.0.0, which brings performance improvements
- Replaced `double-ended-queue` with `denque`, which brings performance improvements
- Add timestamps to debug traces
- Add `socket_initial_delay` option for `socket.setKeepAlive` (#1396)
- Add support for `rediss` protocol in url (#1282)
## v2.8.0 - 31 Jul, 2017
Features
- Accept UPPER_CASE commands in send_command
- Add arbitrary commands to the prototype by using `Redis.addCommand(name)`
Bugfixes
- Fixed not always copying subscribe unsubscribe arguments
- Fixed emitting internal errors while reconnecting with auth
- Fixed crashing with invalid url option
## v2.7.1 - 14 Mar, 2017
Bugfixes
- Fixed monitor mode not working in combination with IPv6 (2.6.0 regression)
## v2.7.0 - 11 Mar, 2017
Features
- All returned errors are from now a subclass of `RedisError`.
Bugfixes
- Fixed rename_commands not accepting `null` as value
- Fixed `AbortError`s and `AggregateError`s not showing the error message in the stack trace
## v2.6.5 - 15 Jan, 2017
Bugfixes
- Fixed parser not being reset in case the redis connection closed ASAP for overcoming of output buffer limits
- Fixed parser reset if (p)message_buffer listener is attached
## v2.6.4 - 12 Jan, 2017
Bugfixes
- Fixed monitor mode not working in combination with IPv6, sockets or lua scripts (2.6.0 regression)
## v2.6.3 - 31 Oct, 2016
Bugfixes
- Do not change the tls setting to camel_case
- Fix domain handling in combination with the offline queue (2.5.3 regression)
## v2.6.2 - 16 Jun, 2016
Bugfixes
- Fixed individual callbacks of a transaction not being called (2.6.0 regression)
## v2.6.1 - 02 Jun, 2016
Bugfixes
- Fixed invalid function name being exported
## v2.6.0 - 01 Jun, 2016
In addition to the pre-releases the following changes exist in v.2.6.0:
Features
- Updated [redis-parser](https://github.com/NodeRedis/node-redis-parser) dependency ([changelog](https://github.com/NodeRedis/node-redis-parser/releases/tag/v.2.0.0))
- The JS parser is from now on the new default as it is a lot faster than the hiredis parser
- This is no BC as there is no changed behavior for the user at all but just a performance improvement. Explicitly requireing the Hiredis parser is still possible.
- Added name property to all Redis functions (Node.js >= 4.0)
- Improved stack traces in development and debug mode
Bugfixes
- Reverted support for `__proto__` (v.2.6.0-2) to prevent and breaking change
Deprecations
- The `parser` option is deprecated and should be removed. The built-in Javascript parser is a lot faster than the hiredis parser and has more features
## v2.6.0-2 - 29 Apr, 2016
Features
- Added support for the new [CLIENT REPLY ON|OFF|SKIP](http://redis.io/commands/client-reply) command (Redis v.3.2)
- Added support for camelCase
- The Node.js landscape default is to use camelCase. node_redis is a bit out of the box here
but from now on it is possible to use both, just as you prefer!
- If there's any documented variable missing as camelCased, please open a issue for it
- Improve error handling significantly
- Only emit an error if the error has not already been handled in a callback
- Improved unspecific error messages e.g. "Connection gone from end / close event"
- Added `args` to command errors to improve identification of the error
- Added origin to errors if there's e.g. a connection error
- Added ReplyError class. All Redis errors are from now on going to be of that class
- Added AbortError class. A subclass of AbortError. All unresolved and by node_redis rejected commands are from now on of that class
- Added AggregateError class. If a unresolved and by node_redis rejected command has no callback and
this applies to more than a single command, the errors for the commands without callback are aggregated
to a single error that is emitted in debug_mode in that case.
- Added `message_buffer` / `pmessage_buffer` events. That event is always going to emit a buffer
- Listening to the `message` event at the same time is always going to return the same message as string
- Added callback option to the duplicate function
- Added support for `__proto__` and other reserved keywords as hgetall field
- Updated [redis-commands](https://github.com/NodeRedis/redis-commands) dependency ([changelog](https://github.com/NodeRedis/redis-commands/releases/tag/v.1.2.0))
Bugfixes
- Fixed v.2.5.0 auth command regression (under special circumstances a reconnect would not authenticate properly)
- Fixed v.2.6.0-0 pub sub mode and quit command regressions:
- Entering pub sub mode not working if a earlier called and still running command returned an error
- Unsubscribe callback not called if unsubscribing from all channels and resubscribing right away
- Quit command resulting in an error in some cases
- Fixed special handled functions in batch and multi context not working the same as without (e.g. select and info)
- Be aware that not all commands work in combination with transactions but they all work with batch
- Fixed address always set to 127.0.0.1:6379 in case host / port is set in the `tls` options instead of the general options
## v2.6.0-1 - 01 Apr, 2016
A second pre-release with further fixes. This is likely going to be released as 2.6.0 stable without further changes.
Features
- Added type validations for client.send_command arguments
Bugfixes
- Fixed client.send_command not working properly with every command and every option
- Fixed pub sub mode unsubscribing from all channels in combination with the new `string_numbers` option crashing
- Fixed pub sub mode unsubscribing from all channels not respected while reconnecting
- Fixed pub sub mode events in combination with the `string_numbers` option emitting the number of channels not as number
## v2.6.0-0 - 27 Mar, 2016
This is mainly a very important bug fix release with some smaller features.
Features
- Monitor and pub sub mode now work together with the offline queue
- All commands that were send after a connection loss are now going to be send after reconnecting
- Activating monitor mode does now work together with arbitrary commands including pub sub mode
- Pub sub mode is completely rewritten and all known issues fixed
- Added `string_numbers` option to get back strings instead of numbers
- Quit command is from now on always going to end the connection properly
Bugfixes
- Fixed calling monitor command while other commands are still running
- Fixed monitor and pub sub mode not working together
- Fixed monitor mode not working in combination with the offline queue
- Fixed pub sub mode not working in combination with the offline queue
- Fixed pub sub mode resubscribing not working with non utf8 buffer channels
- Fixed pub sub mode crashing if calling unsubscribe / subscribe in various combinations
- Fixed pub sub mode emitting unsubscribe even if no channels were unsubscribed
- Fixed pub sub mode emitting a message without a message published
- Fixed quit command not ending the connection and resulting in further reconnection if called while reconnecting
The quit command did not end connections earlier if the connection was down at that time and this could have
lead to strange situations, therefor this was fixed to end the connection right away in those cases.
## v2.5.3 - 21 Mar, 2016
Bugfixes
- Revert throwing on invalid data types and print a warning instead
## v2.5.2 - 16 Mar, 2016
Bugfixes
- Fixed breaking changes against Redis 2.4 introduced in 2.5.0 / 2.5.1
## v2.5.1 - 15 Mar, 2016
Bugfixes
- Fixed info command not working anymore with optional section argument
## v2.5.0 - 15 Mar, 2016
Same changelog as the pre-release
## v2.5.0-1 - 07 Mar, 2016
This is a big release with some substantial underlining changes. Therefor this is released as a pre-release and I encourage anyone who's able to, to test this out.
It took way to long to release this one and the next release cycles will be shorter again.
This release is also going to deprecate a couple things to prepare for a future v.3 (it'll still take a while to v.3).
Features
- The parsers moved into the [redis-parser](https://github.com/NodeRedis/node-redis-parser) module and will be maintained in there from now on
- Improve js parser speed significantly for big SUNION/SINTER/LRANGE/ZRANGE
- Improve redis-url parsing to also accept the database-number and options as query parameters as suggested in [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)
- Added a `retry_unfulfilled_commands` option
- Setting this to 'true' results in retrying all commands that were not fulfilled on a connection loss after the reconnect. Use with caution
- Added a `db` option to select the database while connecting (this is [not recommended](https://groups.google.com/forum/#!topic/redis-db/vS5wX8X4Cjg))
- Added a `password` option as alias for auth_pass
- The client.server_info is from now on updated while using the info command
- Gracefuly handle redis protocol errors from now on
- Added a `warning` emitter that receives node_redis warnings like auth not required and deprecation messages
- Added a `retry_strategy` option that replaces all reconnect options
- The reconnecting event from now on also receives:
- The error message why the reconnect happened (params.error)
- The amount of times the client was connected (params.times_connected)
- The total reconnecting time since the last time connected (params.total_retry_time)
- Always respect the command execution order no matter if the reply could be returned sync or not (former exceptions: [#937](https://github.com/NodeRedis/node_redis/issues/937#issuecomment-167525939))
- redis.createClient is now checking input values stricter and detects more faulty input
- Started refactoring internals into individual modules
- Pipelining speed improvements
Bugfixes
- Fixed explicit undefined as a command callback in a multi context
- Fixed hmset failing to detect the first key as buffer or date if the key is of that type
- Fixed do not run toString on an array argument and throw a "invalid data" error instead
- This is not considered as breaking change, as this is likely a error in your code and if you want to have such a behavior you should handle this beforehand
- The same applies to Map / Set and individual Object types
- Fixed redis url not accepting the protocol being omitted or protocols other than the redis protocol for convenience
- Fixed parsing the db keyspace even if the first database does not begin with a zero
- Fixed handling of errors occurring while receiving pub sub messages
- Fixed huge string pipelines crashing NodeJS (Pipeline size above 256mb)
- Fixed rename_commands and prefix option not working together
- Fixed ready being emitted to early in case a slave is still syncing / master down
Deprecations
- Using any command with a argument being set to null or undefined is deprecated
- From v.3.0.0 on using a command with such an argument will return an error instead
- If you want to keep the old behavior please use a precheck in your code that converts the arguments to a string.
- Using SET or SETEX with a undefined or null value will from now on also result in converting the value to "null" / "undefined" to have a consistent behavior. This is not considered as breaking change, as it returned an error earlier.
- Using .end(flush) without the flush parameter is deprecated and the flush parameter should explicitly be used
- From v.3.0.0 on using .end without flush will result in an error
- Using .end without flush means that any command that did not yet return is going to silently fail. Therefor this is considered harmful and you should explicitly silence such errors if you are sure you want this
- Depending on the return value of a command to detect the backpressure is deprecated
- From version 3.0.0 on node_redis might not return true / false as a return value anymore. Please rely on client.should_buffer instead
- The `socket_nodelay` option is deprecated and will be removed in v.3.0.0
- If you want to buffer commands you should use [.batch or .multi](./README.md) instead. This is necessary to reduce the amount of different options and this is very likely reducing your throughput if set to false.
- If you are sure you want to activate the NAGLE algorithm you can still activate it by using client.stream.setNoDelay(false)
- The `max_attempts` option is deprecated and will be removed in v.3.0.0. Please use the `retry_strategy` instead
- The `retry_max_delay` option is deprecated and will be removed in v.3.0.0. Please use the `retry_strategy` instead
- The drain event is deprecated and will be removed in v.3.0.0. Please listen to the stream drain event instead
- The idle event is deprecated and will likely be removed in v.3.0.0. If you rely on this feature please open a new ticket in node_redis with your use case
- Redis < v. 2.6 is not officially supported anymore and might not work in all cases. Please update to a newer redis version as it is not possible to test for these old versions
- Removed non documented command syntax (adding the callback to an arguments array instead of passing it as individual argument)
## v2.4.2 - 27 Nov, 2015
Bugfixes
- Fixed not emitting ready after reconnect with disable_resubscribing ([@maxgalbu](https://github.com/maxgalbu))
## v2.4.1 - 25 Nov, 2015
Bugfixes
- Fixed a js parser regression introduced in 2.4.0 ([@BridgeAR](https://github.com/BridgeAR))
## v2.4.0 - 25 Nov, 2015
Features
- Added `tls` option to initiate a connection to a redis server behind a TLS proxy. Thanks ([@paddybyers](https://github.com/paddybyers))
- Added `prefix` option to auto key prefix any command with the provided prefix ([@luin](https://github.com/luin) & [@BridgeAR](https://github.com/BridgeAR))
- Added `url` option to pass the connection url with the options object ([@BridgeAR](https://github.com/BridgeAR))
- Added `client.duplicate([options])` to duplicate the current client and return a new one with the same options ([@BridgeAR](https://github.com/BridgeAR))
- Improve performance by up to 20% on almost all use cases ([@BridgeAR](https://github.com/BridgeAR))
Bugfixes
- Fixed js parser handling big values slow ([@BridgeAR](https://github.com/BridgeAR))
- The speed is now on par with the hiredis parser.
## v2.3.1 - 18 Nov, 2015
Bugfixes
- Fixed saving buffers with charsets other than utf-8 while using multi ([@BridgeAR](https://github.com/BridgeAR))
- Fixed js parser handling big values very slow ([@BridgeAR](https://github.com/BridgeAR))
- The speed is up to ~500% faster than before but still up to ~50% slower than the hiredis parser.
## v2.3.0 - 30 Oct, 2015
Features
- Improve speed further for: ([@BridgeAR](https://github.com/BridgeAR))
- saving big strings (up to +300%)
- using .multi / .batch (up to +50% / on Node.js 0.10.x +300%)
- saving small buffers
- Increased coverage to 99% ([@BridgeAR](https://github.com/BridgeAR))
- Refactored manual backpressure control ([@BridgeAR](https://github.com/BridgeAR))
- Removed the high water mark and low water mark. Such a mechanism should be implemented by a user instead
- The `drain` event is from now on only emitted if the stream really had to buffer
- Reduced the default connect_timeout to be one hour instead of 24h ([@BridgeAR](https://github.com/BridgeAR))
- Added .path to redis.createClient(options); ([@BridgeAR](https://github.com/BridgeAR))
- Ignore info command, if not available on server ([@ivanB1975](https://github.com/ivanB1975))
Bugfixes
- Fixed a js parser error that could result in a timeout ([@BridgeAR](https://github.com/BridgeAR))
- Fixed .multi / .batch used with Node.js 0.10.x not working properly after a reconnect ([@BridgeAR](https://github.com/BridgeAR))
- Fixed fired but not yet returned commands not being rejected after a connection loss ([@BridgeAR](https://github.com/BridgeAR))
- Fixed connect_timeout not respected if no connection has ever been established ([@gagle](https://github.com/gagle) & [@benjie](https://github.com/benjie))
- Fixed return_buffers in pub sub mode ([@komachi](https://github.com/komachi))
## v2.2.5 - 18 Oct, 2015
Bugfixes
- Fixed undefined options passed to a new instance not accepted (possible with individual .createClient functions) ([@BridgeAR](https://github.com/BridgeAR))
## v2.2.4 - 17 Oct, 2015
Bugfixes
- Fixed unspecific error message for unresolvable commands ([@BridgeAR](https://github.com/BridgeAR))
- Fixed not allowed command error in pubsub mode not being returned in a provided callback ([@BridgeAR](https://github.com/BridgeAR))
- Fixed to many commands forbidden in pub sub mode ([@BridgeAR](https://github.com/BridgeAR))
- Fixed mutation of the arguments array passed to .multi / .batch constructor ([@BridgeAR](https://github.com/BridgeAR))
- Fixed mutation of the options object passed to createClient ([@BridgeAR](https://github.com/BridgeAR))
- Fixed error callback in .multi not called if connection in broken mode ([@BridgeAR](https://github.com/BridgeAR))
## v2.2.3 - 14 Oct, 2015
Bugfixes
- Fixed multi not being executed on Node 0.10.x if node_redis not yet ready ([@BridgeAR](https://github.com/BridgeAR))
## v2.2.2 - 14 Oct, 2015
Bugfixes
- Fixed regular commands not being executed after a .multi until .exec was called ([@BridgeAR](https://github.com/BridgeAR))
## v2.2.1 - 12 Oct, 2015
No code change
## v2.2.0 - 12 Oct, 2015 - The peregrino falcon
The peregrino falcon is the fasted bird on earth and this is what this release is all about: Increased performance for heavy usage by up to **400%** [sic!] and increased overall performance for any command as well. Please check the benchmarks in the [README.md](README.md) for further details.
Features
- Added rename_commands options to handle renamed commands from the redis config ([@digmxl](https://github.com/digmxl) & [@BridgeAR](https://github.com/BridgeAR))
- Added disable_resubscribing option to prevent a client from resubscribing after reconnecting ([@BridgeAR](https://github.com/BridgeAR))
- Increased performance ([@BridgeAR](https://github.com/BridgeAR))
- exchanging built in queue with [@petkaantonov](https://github.com/petkaantonov)'s [double-ended queue](https://github.com/petkaantonov/deque)
- prevent polymorphism
- optimize statements
- Added _.batch_ command, similar to .multi but without transaction ([@BridgeAR](https://github.com/BridgeAR))
- Improved pipelining to minimize the [RTT](http://redis.io/topics/pipelining) further ([@BridgeAR](https://github.com/BridgeAR))
Bugfixes
- Fixed a javascript parser regression introduced in 2.0 that could result in timeouts on high load. ([@BridgeAR](https://github.com/BridgeAR))
- I was not able to write a regression test for this, since the error seems to only occur under heavy load with special conditions. So please have a look for timeouts with the js parser, if you use it and report all issues and switch to the hiredis parser in the meanwhile. If you're able to come up with a reproducable test case, this would be even better :)
- Fixed should_buffer boolean for .exec, .select and .auth commands not being returned and fix a couple special conditions ([@BridgeAR](https://github.com/BridgeAR))
If you do not rely on transactions but want to reduce the RTT you can use .batch from now on. It'll behave just the same as .multi but it does not have any transaction and therefor won't roll back any failed commands.
Both .multi and .batch are from now on going to cache the commands and release them while calling .exec.
Please consider using .batch instead of looping through a lot of commands one by one. This will significantly improve your performance.
Here are some stats compared to ioredis 1.9.1 (Lenovo T450s i7-5600U):
simple set
82,496 op/s » ioredis
112,617 op/s » node_redis
simple get
82,015 op/s » ioredis
105,701 op/s » node_redis
simple get with pipeline
10,233 op/s » ioredis
26,541 op/s » node_redis (using .batch)
lrange 100
7,321 op/s » ioredis
26,155 op/s » node_redis
publish
90,524 op/s » ioredis
112,823 op/s » node_redis
subscribe
43,783 op/s » ioredis
61,889 op/s » node_redis
To conclude: we can proudly say that node_redis is very likely outperforming any other node redis client.
Known issues
- The pub sub system has some flaws and those will be addressed in the next minor release
## v2.1.0 - Oct 02, 2015
Features:
- Addded optional flush parameter to `.end`. If set to true, commands fired after using .end are going to be rejected instead of being ignored. (@crispy1989)
- Addded: host and port can now be provided in a single options object. E.g. redis.createClient({ host: 'localhost', port: 1337, max_attempts: 5 }); (@BridgeAR)
- Speedup common cases (@BridgeAR)
Bugfixes:
- Fix argument mutation while using the array notation with the multi constructor (@BridgeAR)
- Fix multi.hmset key not being type converted if used with an object and key not being a string (@BridgeAR)
- Fix parser errors not being catched properly (@BridgeAR)
- Fix a crash that could occur if a redis server does not return the info command as usual #541 (@BridgeAR)
- Explicitly passing undefined as a callback statement will work again. E.g. client.publish('channel', 'message', undefined); (@BridgeAR)
## v2.0.1 - Sep 24, 2015
Bugfixes:
- Fix argument mutation while using the array notation in combination with keys / callbacks ([#866](.)). (@BridgeAR)
## v2.0.0 - Sep 21, 2015
This is the biggest release that node_redis had since it was released in 2010. A long list of outstanding bugs has been fixed, so we are very happy to present you redis 2.0 and we highly recommend updating as soon as possible.
# What's new in 2.0
- Implemented a "connection is broken" mode if no connection could be established
- node_redis no longer throws under any circumstances, preventing it from terminating applications.
- Multi error handling is now working properly
- Consistent command behavior including multi
- Windows support
- Improved performance
- A lot of code cleanup
- Many bug fixes
- Better user support!
## Features:
- Added a "redis connection is broken" mode after reaching max connection attempts / exceeding connection timeout. (@BridgeAR)
- Added NODE_DEBUG=redis env to activate the debug_mode (@BridgeAR)
- Added a default connection timeout of 24h instead of never timing out as a default (@BridgeAR)
- Added: Network errors and other stream errors will from now on include the error code as `err.code` property (@BridgeAR)
- Added: Errors thrown by redis will now include the redis error code as `err.code` property. (@skeggse & @BridgeAR)
- Added: Errors thrown by node_redis will now include a `err.command` property for the command used (@BridgeAR)
- Added new commands and drop support for deprecated _substr_ (@BridgeAR)
- Added new possibilities how to provide the command arguments (@BridgeAR)
- The entries in the keyspace of the server_info is now an object instead of a string. (@SinisterLight & @BridgeAR)
- Small speedup here and there (e.g. by not using .toLowerCase() anymore) (@BridgeAR)
- Full windows support (@bcoe)
- Increased coverage by 10% and add a lot of tests to make sure everything works as it should. We now reached 97% :-) (@BridgeAR)
- Remove dead code, clean up and refactor very old chunks (@BridgeAR)
- Don't flush the offline queue if reconnecting (@BridgeAR)
- Emit all errors insteaf of throwing sometimes and sometimes emitting them (@BridgeAR)
- _auth_pass_ passwords are now checked to be a valid password (@jcppman & @BridgeAR)
## Bug fixes:
- Don't kill the app anymore by randomly throwing errors sync instead of emitting them (@BridgeAR)
- Don't catch user errors anymore occuring in callbacks (no try callback anymore & more fixes for the parser) (@BridgeAR)
- Early garbage collection of queued items (@dohse)
- Fix js parser returning errors as strings (@BridgeAR)
- Do not wrap errors into other errors (@BridgeAR)
- Authentication failures are now returned in the callback instead of being emitted (@BridgeAR)
- Fix a memory leak on reconnect (@rahar)
- Using `send_command` directly may now also be called without the args as stated in the [README.md](./README.md) (@BridgeAR)
- Fix the multi.exec error handling (@BridgeAR)
- Fix commands being inconsistent and behaving wrong (@BridgeAR)
- Channel names with spaces are now properly resubscribed after a reconnection (@pbihler)
- Do not try to reconnect after the connection timeout has been exceeded (@BridgeAR)
- Ensure the execution order is observed if using .eval (@BridgeAR)
- Fix commands not being rejected after calling .quit (@BridgeAR)
- Fix .auth calling the callback twice if already connected (@BridgeAR)
- Fix detect_buffers not working in pub sub mode and while monitoring (@BridgeAR)
- Fix channel names always being strings instead of buffers while return_buffers is true (@BridgeAR)
- Don't print any debug statements if not asked for (@BridgeAR)
- Fix a couple small other bugs
## Breaking changes:
1. redis.send_command commands have to be lower case from now on. This does only apply if you use `.send_command` directly instead of the convenient methods like `redis.command`.
2. Error messages have changed quite a bit. If you depend on a specific wording please check your application carfully.
3. Errors are from now on always either returned if a callback is present or emitted. They won't be thrown (neither sync, nor async).
4. The Multi error handling has changed a lot!
- All errors are from now on errors instead of strings (this only applied to the js parser).
- If an error occurs while queueing the commands an EXECABORT error will be returned including the failed commands as `.errors` property instead of an array with errors.
- If an error occurs while executing the commands and that command has a callback it'll return the error as first parameter (`err, undefined` instead of `null, undefined`).
- All the errors occuring while executing the commands will stay in the result value as error instance (if you used the js parser before they would have been strings). Be aware that the transaction won't be aborted if those error occurr!
- If `multi.exec` does not have a callback and an EXECABORT error occurrs, it'll emit that error instead.
5. If redis can't connect to your redis server it'll give up after a certain point of failures (either max connection attempts or connection timeout exceeded). If that is the case it'll emit an CONNECTION_BROKEN error. You'll have to initiate a new client to try again afterwards.
6. The offline queue is not flushed anymore on a reconnect. It'll stay until node_redis gives up trying to reach the server or until you close the connection.
7. Before this release node_redis catched user errors and threw them async back. This is not the case anymore! No user behavior of what so ever will be tracked or catched.
8. The keyspace of `redis.server_info` (db0...) is from now on an object instead of an string.
NodeRedis also thanks @qdb, @tobek, @cvibhagool, @frewsxcv, @davidbanham, @serv, @vitaliylag, @chrishamant, @GamingCoder and all other contributors that I may have missed for their contributions!
From now on we'll push new releases more frequently out and fix further long outstanding things and implement new features.
## v1.0.0 - Aug 30, 2015
- Huge issue and pull-request cleanup. Thanks Blain! (@blainsmith)
- [#658](https://github.com/NodeRedis/node_redis/pull/658) Client now parses URL-format connection strings (e.g., redis://foo:pass@127.0.0.1:8080) (@kuwabarahiroshi)
- [#749](https://github.com/NodeRedis/node_redis/pull/749) Fix reconnection bug when client is in monitoring mode (@danielbprice)
- [#786](https://github.com/NodeRedis/node_redis/pull/786) Refactor createClient. Fixes #651 (@BridgeAR)
- [#793](https://github.com/NodeRedis/node_redis/pull/793) Refactor tests and improve test coverage (@erinspice, @bcoe)
- [#733](https://github.com/NodeRedis/node_redis/pull/733) Fixes detect_buffers functionality in the context of exec. Fixes #732, #263 (@raydog)
- [#785](https://github.com/NodeRedis/node_redis/pull/785) Tiny speedup by using 'use strict' (@BridgeAR)
- Fix extraneous error output due to pubsub tests (Mikael Kohlmyr)
## v0.12.1 - Aug 10, 2014
- Fix IPv6/IPv4 family selection in node 0.11+ (Various)
## v0.12.0 - Aug 9, 2014
- Fix unix socket support (Jack Tang)
- Improve createClient argument handling (Jack Tang)
## v0.11.0 - Jul 10, 2014
- IPv6 Support. (Yann Stephan)
- Revert error emitting and go back to throwing errors. (Bryce Baril)
- Set socket_keepalive to prevent long-lived client timeouts. (mohit)
- Correctly reset retry timer. (ouotuo)
- Domains protection from bad user exit. (Jake Verbaten)
- Fix reconnection socket logic to prevent misqueued entries. (Iain Proctor)
## v0.10.3 - May 22, 2014
- Update command list to match Redis 2.8.9 (Charles Feng)
## v0.10.2 - May 18, 2014
- Better binary key handling for HGETALL. (Nick Apperson)
- Fix test not resetting `error` handler. (CrypticSwarm)
- Fix SELECT error semantics. (Bryan English)
## v0.10.1 - February 17, 2014
- Skip plucking redis version from the INFO stream if INFO results weren't provided. (Robert Sköld)
## v0.10.0 - December 21, 2013
- Instead of throwing errors asynchronously, emit errors on client. (Bryce Baril)
## v0.9.2 - December 15, 2013
- Regenerate commands for new 2.8.x Redis commands. (Marek Ventur)
- Correctly time reconnect counts when using 'auth'. (William Hockey)
## v0.9.1 - November 23, 2013
- Allow hmset to accept numeric keys. (Alex Stokes)
- Fix TypeError for multiple MULTI/EXEC errors. (Kwangsu Kim)
## v0.9.0 - October 17, 2013
- Domains support. (Forrest L Norvell)
## v0.8.6 - October 2, 2013
- If error is already an Error, don't wrap it in another Error. (Mathieu M-Gosselin)
- Fix retry delay logic (Ian Babrou)
- Return Errors instead of strings where Errors are expected (Ian Babrou)
- Add experimental `.unref()` method to RedisClient (Bryce Baril / Olivier Lalonde)
- Strengthen checking of reply to prevent conflating "message" or "pmessage" fields with pub_sub replies. (Bryce Baril)
## v0.8.5 - September 26, 2013
- Add `auth_pass` option to connect and immediately authenticate (Henrik Peinar)
## v0.8.4 - June 24, 2013
Many contributed features and fixes, including:
- Ignore password set if not needed. (jbergknoff)
- Improved compatibility with 0.10.X for tests and client.end() (Bryce Baril)
- Protect connection retries from application exceptions. (Amos Barreto)
- Better exception handling for Multi/Exec (Thanasis Polychronakis)
- Renamed pubsub mode to subscriber mode (Luke Plaster)
- Treat SREM like SADD when passed an array (Martin Ciparelli)
- Fix empty unsub/punsub TypeError (Jeff Barczewski)
- Only attempt to run a callback if it one was provided (jifeng)
## v0.8.3 - April 09, 2013
Many contributed features and fixes, including:
- Fix some tests for Node.js version 0.9.x+ changes (Roman Ivanilov)
- Fix error when commands submitted after idle event handler (roamm)
- Bypass Redis for no-op SET/SETEX commands (jifeng)
- Fix HMGET + detect_buffers (Joffrey F)
- Fix CLIENT LOAD functionality (Jonas Dohse)
- Add percentage outputs to diff_multi_bench_output.js (Bryce Baril)
- Add retry_max_delay option (Tomasz Durka)
- Fix parser off-by-one errors with nested multi-bulk replies (Bryce Baril)
- Prevent parser from sinking application-side exceptions (Bryce Baril)
- Fix parser incorrect buffer skip when parsing multi-bulk errors (Bryce Baril)
- Reverted previous change with throwing on non-string values with HMSET (David Trejo)
- Fix command queue sync issue when using pubsub (Tom Leach)
- Fix compatibility with two-word Redis commands (Jonas Dohse)
- Add EVAL with array syntax (dmoena)
- Fix tests due to Redis reply order changes in 2.6.5+ (Bryce Baril)
- Added a test for the SLOWLOG command (Nitesh Sinha)
- Fix SMEMBERS order dependency in test broken by Redis changes (Garrett Johnson)
- Update commands for new Redis commands (David Trejo)
- Prevent exception from SELECT on subscriber reconnection (roamm)
## v0.8.2 - November 11, 2012
Another version bump because 0.8.1 didn't get applied properly for some mysterious reason.
Sorry about that.
Changed name of "faster" parser to "javascript".
## v0.8.1 - September 11, 2012
Important bug fix for null responses (Jerry Sievert)
## v0.8.0 - September 10, 2012
Many contributed features and fixes, including:
- Pure JavaScript reply parser that is usually faster than hiredis (Jerry Sievert)
- Remove hiredis as optionalDependency from package.json. It still works if you want it.
- Restore client state on reconnect, including select, subscribe, and monitor. (Ignacio Burgueño)
- Fix idle event (Trae Robrock)
- Many documentation improvements and bug fixes (David Trejo)
## v0.7.2 - April 29, 2012
Many contributed fixes. Thank you, contributors.
- [GH-190] - pub/sub mode fix (Brian Noguchi)
- [GH-165] - parser selection fix (TEHEK)
- numerous documentation and examples updates
- auth errors emit Errors instead of Strings (David Trejo)
## v0.7.1 - November 15, 2011
Fix regression in reconnect logic.
Very much need automated tests for reconnection and queue logic.
## v0.7.0 - November 14, 2011
Many contributed fixes. Thanks everybody.
- [GH-127] - properly re-initialize parser on reconnect
- [GH-136] - handle passing undefined as callback (Ian Babrou)
- [GH-139] - properly handle exceptions thrown in pub/sub event handlers (Felix Geisendörfer)
- [GH-141] - detect closing state on stream error (Felix Geisendörfer)
- [GH-142] - re-select database on reconnection (Jean-Hugues Pinson)
- [GH-146] - add sort example (Maksim Lin)
Some more goodies:
- Fix bugs with node 0.6
- Performance improvements
- New version of `multi_bench.js` that tests more realistic scenarios
- [GH-140] - support optional callback for subscribe commands
- Properly flush and error out command queue when connection fails
- Initial work on reconnection thresholds
## v0.6.7 - July 30, 2011
(accidentally skipped v0.6.6)
Fix and test for [GH-123]
Passing an Array as as the last argument should expand as users
expect. The old behavior was to coerce the arguments into Strings,
which did surprising things with Arrays.
## v0.6.5 - July 6, 2011
Contributed changes:
- Support SlowBuffers (Umair Siddique)
- Add Multi to exports (Louis-Philippe Perron)
- Fix for drain event calculation (Vladimir Dronnikov)
Thanks!
## v0.6.4 - June 30, 2011
Fix bug with optional callbacks for hmset.
## v0.6.2 - June 30, 2011
Bugs fixed:
- authentication retry while server is loading db (danmaz74) [GH-101]
- command arguments processing issue with arrays
New features:
- Auto update of new commands from redis.io (Dave Hoover)
- Performance improvements and backpressure controls.
- Commands now return the true/false value from the underlying socket write(s).
- Implement command_queue high water and low water for more better control of queueing.
See `examples/backpressure_drain.js` for more information.
## v0.6.1 - June 29, 2011
Add support and tests for Redis scripting through EXEC command.
Bug fix for monitor mode. (forddg)
Auto update of new commands from redis.io (Dave Hoover)
## v0.6.0 - April 21, 2011
Lots of bugs fixed.
- connection error did not properly trigger reconnection logic [GH-85]
- client.hmget(key, [val1, val2]) was not expanding properly [GH-66]
- client.quit() while in pub/sub mode would throw an error [GH-87]
- client.multi(['hmset', 'key', {foo: 'bar'}]) fails [GH-92]
- unsubscribe before subscribe would make things very confused [GH-88]
- Add BRPOPLPUSH [GH-79]
## v0.5.11 - April 7, 2011
Added DISCARD
I originally didn't think DISCARD would do anything here because of the clever MULTI interface, but somebody
pointed out to me that DISCARD can be used to flush the WATCH set.
## v0.5.10 - April 6, 2011
Added HVALS
## v0.5.9 - March 14, 2011
Fix bug with empty Array arguments - Andy Ray
## v0.5.8 - March 14, 2011
Add `MONITOR` command and special monitor command reply parsing.
## v0.5.7 - February 27, 2011
Add magical auth command.
Authentication is now remembered by the client and will be automatically sent to the server
on every connection, including any reconnections.
## v0.5.6 - February 22, 2011
Fix bug in ready check with `return_buffers` set to `true`.
Thanks to Dean Mao and Austin Chau.
## v0.5.5 - February 16, 2011
Add probe for server readiness.
When a Redis server starts up, it might take a while to load the dataset into memory.
During this time, the server will accept connections, but will return errors for all non-INFO
commands. Now node_redis will send an INFO command whenever it connects to a server.
If the info command indicates that the server is not ready, the client will keep trying until
the server is ready. Once it is ready, the client will emit a "ready" event as well as the
"connect" event. The client will queue up all commands sent before the server is ready, just
like it did before. When the server is ready, all offline/non-ready commands will be replayed.
This should be backward compatible with previous versions.
To disable this ready check behavior, set `options.no_ready_check` when creating the client.
As a side effect of this change, the key/val params from the info command are available as
`client.server_options`. Further, the version string is decomposed into individual elements
in `client.server_options.versions`.
## v0.5.4 - February 11, 2011
Fix excess memory consumption from Queue backing store.
Thanks to Gustaf Sjöberg.
## v0.5.3 - February 5, 2011
Fix multi/exec error reply callback logic.
Thanks to Stella Laurenzo.
## v0.5.2 - January 18, 2011
Fix bug where unhandled error replies confuse the parser.
## v0.5.1 - January 18, 2011
Fix bug where subscribe commands would not handle redis-server startup error properly.
## v0.5.0 - December 29, 2010
Some bug fixes:
- An important bug fix in reconnection logic. Previously, reply callbacks would be invoked twice after
a reconnect.
- Changed error callback argument to be an actual Error object.
New feature:
- Add friendly syntax for HMSET using an object.
## v0.4.1 - December 8, 2010
Remove warning about missing hiredis. You probably do want it though.
## v0.4.0 - December 5, 2010
Support for multiple response parsers and hiredis C library from Pieter Noordhuis.
Return Strings instead of Buffers by default.
Empty nested mb reply bug fix.
## v0.3.9 - November 30, 2010
Fix parser bug on failed EXECs.
## v0.3.8 - November 10, 2010
Fix for null MULTI response when WATCH condition fails.
## v0.3.7 - November 9, 2010
Add "drain" and "idle" events.
## v0.3.6 - November 3, 2010
Add all known Redis commands from Redis master, even ones that are coming in 2.2 and beyond.
Send a friendlier "error" event message on stream errors like connection refused / reset.
## v0.3.5 - October 21, 2010
A few bug fixes.
- Fixed bug with `nil` multi-bulk reply lengths that showed up with `BLPOP` timeouts.
- Only emit `end` once when connection goes away.
- Fixed bug in `test.js` where driver finished before all tests completed.
## unversioned wasteland
See the git history for what happened before.
node-redis-3.0.2/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000006242 13620024551 0016346 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making
participation in our project and our community a harassment-free experience for everyone, regardless of age, body size,
disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take
appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the
project or its community. Examples of representing a project or community include using an official project e-mail address,
posting via an official social media account, or acting as an appointed representative at an online or offline event.
Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at
`redis[AT]invertase.io`. The project team will review and investigate all complaints, and will respond in a way that it
deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the
reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent
repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
[http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
node-redis-3.0.2/CONTRIBUTING.md 0000664 0000000 0000000 00000012207 13620024551 0015776 0 ustar 00root root 0000000 0000000 # Introduction
First, thank you for considering contributing to Node Redis! It's people like you that make the open source community such a great community! 😊
We welcome any type of contribution, not just code. You can help with;
- **QA**: file bug reports, the more details you can give the better (e.g. platform versions, screenshots sdk versions & logs)
- **Docs**: improve reference coverage, add more examples, fix typos or anything else you can spot.
- **Code**: take a look at the open issues and help triage them.
- **Donations**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/node-redis).
---
## Project Guidelines
As maintainers of this project, we want to ensure that the project lives and continues to grow. Not blocked by any
singular person's time.
One of the simplest ways of doing this is by encouraging a larger set of shallow contributors. Through this we hope to
mitigate the problems of a project that needs updates but there is no-one who has the power to do so.
### Continuous Deployment
Coming soon.
### How can we help you get comfortable contributing?
It is normal for a first pull request to be a potential fix for a problem but moving on from there to helping the
project's direction can be difficult.
We try to help contributors cross that barrier by offering good first step issues (labelled `good-first-issue`). These
issues can be fixed without feeling like you are stepping on toes. Generally, these should be non-critical issues that
are well defined. They will be purposely avoided by mature contributors to the project, to make space for others.
Additionally issues labelled `needs-triage` or `help-wanted` can also be picked up, these may not necessarily require
code changes but rather help with debugging and finding the cause of the issue whether it's a bug or a users incorrect
setup of the library or project.
We aim to keep all project discussion inside GitHub issues. This is to make sure valuable discussion is accessible via
search. If you have questions about how to use the library, or how the project is running - GitHub issues are the goto
tool for this project.
### Our expectations on you as a contributor
You shouldn't feel bad for not contributing to open source. We want contributors like yourself to provide ideas, keep
the ship shipping and to take some of the load from others. It is non-obligatory; we’re here to get things done in an
enjoyable way. :trophy:
We only ask that you follow the conduct guidelines set out in our [Code of Conduct](/CODE_OF_CONDUCT.md) throughout your
contribution journey.
### What about if you have problems that cannot be discussed in public?
You can reach out to us directly via email (`redis[AT]invertase.io`) or direct message us on
[Twitter](https://twitter.com/NodeRedis) if you'd like to discuss something privately.
#### Project Maintainers
- Mike Diarmid ([Salakar](https://github.com/Salakar)) @ [Invertase](https://github.com/invertase)
- Twitter: [@mikediarmid](https://twitter.com/mikediarmid)
- Elliot Hesp ([Ehesp](https://github.com/Ehesp)) @ [Invertase](https://github.com/invertase)
- Twitter: [@elliothesp](https://twitter.com/elliothesp)
- Ruben Bridgewater ([BridgeAR](https://github.com/BridgeAR))
- Twitter: [@BridgeAR](https://twitter.com/BridgeAR)
Huge thanks to the original author of Node Redis, [Matthew Ranney](https://github.com/mranney) and also to
[Ruben Bridgewater](https://github.com/BridgeAR) for handing over this project over to new maintainers so it could be
continuously maintained.
---
## Code Guidelines
### Your First Contribution
Working on your first Pull Request? You can learn how from this _free_ series,
[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
### Testing Code
Node Redis has a full test suite with coverage setup.
To run the tests use the `npm test` command. To check detailed coverage locally run the `npm run coverage` command after
testing and open the generated `./coverage/index.html` in your browser.
### Submitting code for review
The bigger the pull request, the longer it will take to review and merge. Where possible try to break down large pull
requests into smaller chunks that are easier to review and merge. It is also always helpful to have some context for
your pull request. What was the purpose? Why does it matter to you? What problem are you trying to solve? Tag in any linked issues.
To aid review we also ask that you fill out the pull request template as much as possible.
> Use a `draft` pull request if your pull request is not complete or ready for review.
### Code review process
Pull Requests to the protected branches require two or more peer-review approvals and passing status checks to be able
to be merged.
When reviewing a Pull Request please check the following steps on top of the existing automated checks:
- Does the it provide or update the docs if docs changes are required?
- Have the tests been updated or new tests been added to test any newly implemented or changed functionality?
- Is the testing coverage ok and not worse than previously?
node-redis-3.0.2/LICENSE 0000664 0000000 0000000 00000002101 13620024551 0014542 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2016-present Node Redis contributors.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
node-redis-3.0.2/README.md 0000664 0000000 0000000 00000133767 13620024551 0015043 0 ustar 00root root 0000000 0000000
Node Redis
A high performance Node.js Redis client.
---
---
## Installation
```bash
npm install redis
```
## Usage
#### Example
```js
const redis = require("redis");
const client = redis.createClient();
client.on("error", function(error) {
console.error(error);
});
client.set("key", "value", redis.print);
client.get("key", redis.print);
```
Note that the API is entirely asynchronous. To get data back from the server,
you'll need to use a callback.
### Promises
Node Redis currently doesn't natively support promises (this is coming in v4), however you can wrap the methods you
want to use with promises using the built-in Node.js `util.promisify` method on Node.js >= v8;
```js
const { promisify } = require("util");
const getAsync = promisify(client.get).bind(client);
getAsync.then(console.log).catch(console.error);
```
### Commands
This library is a 1 to 1 mapping of the [Redis commands](https://redis.io/commands).
Each Redis command is exposed as a function on the `client` object.
All functions take either an `args` Array plus optional `callback` Function or
a variable number of individual arguments followed by an optional callback.
Examples:
```js
client.hmset(["key", "foo", "bar"], function(err, res) {
// ...
});
// Works the same as
client.hmset("key", ["foo", "bar"], function(err, res) {
// ...
});
// Or
client.hmset("key", "foo", "bar", function(err, res) {
// ...
});
```
Care should be taken with user input if arrays are possible (via body-parser, query string or other method), as single arguments could be unintentionally interpreted as multiple args.
Note that in either form the `callback` is optional:
```js
client.set("foo", "bar");
client.set(["hello", "world"]);
```
If the key is missing, reply will be null. Only if the [Redis Command
Reference](http://redis.io/commands) states something else it will not be null.
```js
client.get("missing_key", function(err, reply) {
// reply is null when the key is missing
console.log(reply);
});
```
Minimal parsing is done on the replies. Commands that return a integer return
JavaScript Numbers, arrays return JavaScript Array. `HGETALL` returns an Object
keyed by the hash keys. All strings will either be returned as string or as
buffer depending on your setting. Please be aware that sending null, undefined
and Boolean values will result in the value coerced to a string!
## API
### Connection and other Events
`client` will emit some events about the state of the connection to the Redis server.
#### `"ready"`
`client` will emit `ready` once a connection is established. Commands issued
before the `ready` event are queued, then replayed just before this event is
emitted.
#### `"connect"`
`client` will emit `connect` as soon as the stream is connected to the server.
#### `"reconnecting"`
`client` will emit `reconnecting` when trying to reconnect to the Redis server
after losing the connection. Listeners are passed an object containing `delay`
(in ms from the previous try) and `attempt` (the attempt #) attributes.
#### `"error"`
`client` will emit `error` when encountering an error connecting to the Redis
server or when any other in Node Redis occurs. If you use a command without
callback and encounter a ReplyError it is going to be emitted to the error
listener.
So please attach the error listener to Node Redis.
#### `"end"`
`client` will emit `end` when an established Redis server connection has closed.
#### `"warning"`
`client` will emit `warning` when password was set but none is needed and if a
deprecated option / function / similar is used.
### redis.createClient()
If you have `redis-server` running on the same machine as node, then the
defaults for port and host are probably fine and you don't need to supply any
arguments. `createClient()` returns a `RedisClient` object. Otherwise,
`createClient()` accepts these arguments:
- `redis.createClient([options])`
- `redis.createClient(unix_socket[, options])`
- `redis.createClient(redis_url[, options])`
- `redis.createClient(port[, host][, options])`
**Tip:** If the Redis server runs on the same machine as the client consider
using unix sockets if possible to increase throughput.
**Note:** Using `'rediss://...` for the protocol in a `redis_url` will enable a TLS socket connection. However, additional TLS options will need to be passed in `options`, if required.
#### `options` object properties
| Property | Default | Description |
| -------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| host | 127.0.0.1 | IP address of the Redis server |
| port | 6379 | Port of the Redis server |
| path | null | The UNIX socket string of the Redis server |
| url | null | The URL of the Redis server. Format: `[redis[s]:]//[[user][:password@]][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]` (More info avaliable at [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)). |
| string_numbers | null | Set to `true`, Node Redis will return Redis number values as Strings instead of javascript Numbers. Useful if you need to handle big numbers (above `Number.MAX_SAFE_INTEGER === 2^53`). Hiredis is incapable of this behavior, so setting this option to `true` will result in the built-in javascript parser being used no matter the value of the `parser` option. |
| return_buffers | false | If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings. |
| detect_buffers | false | If set to `true`, then replies will be sent to callbacks as Buffers. This option lets you switch between Buffers and Strings on a per-command basis, whereas `return_buffers` applies to every command on a client. **Note**: This doesn't work properly with the pubsub mode. A subscriber has to either always return Strings or Buffers. |
| socket_keepalive | true | If set to `true`, the keep-alive functionality is enabled on the underlying socket. |
| socket_initial_delay | 0 | Initial Delay in milliseconds, and this will also behave the interval keep alive message sending to Redis. |
| no_ready_check | false | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server will not respond to any commands. To work around this, Node Redis has a "ready check" which sends the `INFO` command to the server. The response from the `INFO` command indicates whether the server is ready for more commands. When ready, `node_redis` emits a `ready` event. Setting `no_ready_check` to `true` will inhibit this check. |
| enable_offline_queue | true | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection has been established. Setting `enable_offline_queue` to `false` will disable this feature and the callback will be executed immediately with an error, or an error will be emitted if no callback is specified. |
| retry_unfulfilled_commands | false | If set to `true`, all commands that were unfulfilled while the connection is lost will be retried after the connection has been reestablished. Use this with caution if you use state altering commands (e.g. `incr`). This is especially useful if you use blocking commands. |
| password | null | If set, client will run Redis auth command on connect. Alias `auth_pass` **Note** Node Redis < 2.5 must use `auth_pass` |
| db | null | If set, client will run Redis `select` command on connect. |
| family | IPv4 | You can force using IPv6 if you set the family to 'IPv6'. See Node.js [net](https://nodejs.org/api/net.html) or [dns](https://nodejs.org/api/dns.html) modules on how to use the family type. |
| disable_resubscribing | false | If set to `true`, a client won't resubscribe after disconnecting. |
| rename_commands | null | Passing an object with renamed commands to use instead of the original functions. For example, if you renamed the command KEYS to "DO-NOT-USE" then the rename_commands object would be: `{ KEYS : "DO-NOT-USE" }` . See the [Redis security topics](http://redis.io/topics/security) for more info. |
| tls | null | An object containing options to pass to [tls.connect](http://nodejs.org/api/tls.html#tls_tls_connect_port_host_options_callback) to set up a TLS connection to Redis (if, for example, it is set up to be accessible via a tunnel). |
| prefix | null | A string used to prefix all used keys (e.g. `namespace:test`). Please be aware that the `keys` command will not be prefixed. The `keys` command has a "pattern" as argument and no key and it would be impossible to determine the existing keys in Redis if this would be prefixed. |
| retry_strategy | function | A function that receives an options object as parameter including the retry `attempt`, the `total_retry_time` indicating how much time passed since the last time connected, the `error` why the connection was lost and the number of `times_connected` in total. If you return a number from this function, the retry will happen exactly after that time in milliseconds. If you return a non-number, no further retry will happen and all offline commands are flushed with errors. Return an error to return that specific error to all offline commands. Example below. |
**`detect_buffers` example:**
```js
const redis = require("redis");
const client = redis.createClient({ detect_buffers: true });
client.set("foo_rand000000000000", "OK");
// This will return a JavaScript String
client.get("foo_rand000000000000", function(err, reply) {
console.log(reply.toString()); // Will print `OK`
});
// This will return a Buffer since original key is specified as a Buffer
client.get(new Buffer("foo_rand000000000000"), function(err, reply) {
console.log(reply.toString()); // Will print ``
});
```
**`retry_strategy` example:**
```js
const client = redis.createClient({
retry_strategy: function(options) {
if (options.error && options.error.code === "ECONNREFUSED") {
// End reconnecting on a specific error and flush all commands with
// a individual error
return new Error("The server refused the connection");
}
if (options.total_retry_time > 1000 * 60 * 60) {
// End reconnecting after a specific timeout and flush all commands
// with a individual error
return new Error("Retry time exhausted");
}
if (options.attempt > 10) {
// End reconnecting with built in error
return undefined;
}
// reconnect after
return Math.min(options.attempt * 100, 3000);
},
});
```
### client.auth(password[, callback])
When connecting to a Redis server that requires authentication, the `AUTH`
command must be sent as the first command after connecting. This can be tricky
to coordinate with reconnections, the ready check, etc. To make this easier,
`client.auth()` stashes `password` and will send it after each connection,
including reconnections. `callback` is invoked only once, after the response to
the very first `AUTH` command sent.
NOTE: Your call to `client.auth()` should not be inside the ready handler. If
you are doing this wrong, `client` will emit an error that looks
something like this `Error: Ready check failed: ERR operation not permitted`.
### client.quit(callback)
This sends the quit command to the redis server and ends cleanly right after all
running commands were properly handled. If this is called while reconnecting
(and therefore no connection to the redis server exists) it is going to end the
connection right away instead of resulting in further reconnections! All offline
commands are going to be flushed with an error in that case.
### client.end(flush)
Forcibly close the connection to the Redis server. Note that this does not wait
until all replies have been parsed. If you want to exit cleanly, call
`client.quit()` as mentioned above.
You should set flush to true, if you are not absolutely sure you do not care
about any other commands. If you set flush to false all still running commands
will silently fail.
This example closes the connection to the Redis server before the replies have
been read. You probably don't want to do this:
```js
const redis = require("redis");
const client = redis.createClient();
client.set("hello", "world", function(err) {
// This will either result in an error (flush parameter is set to true)
// or will silently fail and this callback will not be called at all (flush set to false)
console.error(err);
});
// No further commands will be processed
client.end(true);
client.get("hello", function(err) {
console.error(err); // => 'The connection has already been closed.'
});
```
`client.end()` without the flush parameter set to true should NOT be used in production!
### Error Handling
Currently the following `Error` subclasses exist:
- `RedisError`: _All errors_ returned by the client
- `ReplyError` subclass of `RedisError`: All errors returned by **Redis** itself
- `AbortError` subclass of `RedisError`: All commands that could not finish due
to what ever reason
- `ParserError` subclass of `RedisError`: Returned in case of a parser error
(this should not happen)
- `AggregateError` subclass of `AbortError`: Emitted in case multiple unresolved
commands without callback got rejected in debug_mode instead of lots of
`AbortError`s.
All error classes are exported by the module.
#### Example
```js
const assert = require("assert");
const redis = require("redis");
const { AbortError, AggregateError, ReplyError } = require("redis");
const client = redis.createClient();
client.on("error", function(err) {
assert(err instanceof Error);
assert(err instanceof AbortError);
assert(err instanceof AggregateError);
// The set and get are aggregated in here
assert.strictEqual(err.errors.length, 2);
assert.strictEqual(err.code, "NR_CLOSED");
});
client.set("foo", "bar", "baz", function(err, res) {
// Too many arguments
assert(err instanceof ReplyError); // => true
assert.strictEqual(err.command, "SET");
assert.deepStrictEqual(err.args, ["foo", 123, "bar"]);
redis.debug_mode = true;
client.set("foo", "bar");
client.get("foo");
process.nextTick(function() {
// Force closing the connection while the command did not yet return
client.end(true);
redis.debug_mode = false;
});
});
```
Every `ReplyError` contains the `command` name in all-caps and the arguments (`args`).
If Node Redis emits a library error because of another error, the triggering
error is added to the returned error as `origin` attribute.
**_Error codes_**
Node Redis returns a `NR_CLOSED` error code if the clients connection dropped.
If a command unresolved command got rejected a `UNCERTAIN_STATE` code is
returned. A `CONNECTION_BROKEN` error code is used in case Node Redis gives up
to reconnect.
### client.unref()
Call `unref()` on the underlying socket connection to the Redis server, allowing
the program to exit once no more commands are pending.
This is an **experimental** feature, and only supports a subset of the Redis
protocol. Any commands where client state is saved on the Redis server, e.g.
`*SUBSCRIBE` or the blocking `BL*` commands will _NOT_ work with `.unref()`.
```js
const redis = require("redis");
const client = redis.createClient();
/*
* Calling unref() will allow this program to exit immediately after the get
* command finishes. Otherwise the client would hang as long as the
* client-server connection is alive.
*/
client.unref();
client.get("foo", function(err, value) {
if (err) throw err;
console.log(value);
});
```
### Hash Commands
Most Redis commands take a single String or an Array of Strings as arguments,
and replies are sent back as a single String or an Array of Strings. When
dealing with hash values, there are a couple of useful exceptions to this.
#### client.hgetall(hash, callback)
The reply from an `HGETALL` command will be converted into a JavaScript Object. That way you can interact with the
responses using JavaScript syntax.
**Example:**
```js
client.hmset("key", "foo", "bar", "hello", "world");
client.hgetall("hosts", function(err, value) {
console.log(value.foo); // > "bar"
console.log(value.hello); // > "world"
});
```
#### client.hmset(hash, key1, val1, ...keyN, valN, [callback])
Multiple values may also be set by supplying more arguments.
**Example:**
```js
// key
// 1) foo => bar
// 2) hello => world
client.HMSET("key", "foo", "bar", "hello", "world");
```
### PubSub
#### Example
This example opens two client connections, subscribes to a channel on one of them, and publishes to that
channel on the other.
```js
const redis = require("redis");
const subscriber = redis.createClient();
const publisher = redis.createClient();
let messageCount = 0;
subscriber.on("subscribe", function(channel, count) {
publisher.publish("a channel", "a message");
publisher.publish("a channel", "another message");
});
subscriber.on("message", function(channel, message) {
messageCount += 1;
console.log("Subscriber received message in channel '" + channel + "': " + message);
if (messageCount === 2) {
subscriber.unsubscribe();
subscriber.quit();
publisher.quit();
}
});
subscriber.subscribe("a channel");
```
When a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into
a `"subscriber"` mode. At that point, the only valid commands are those that modify the subscription
set, and quit (also ping on some redis versions). When
the subscription set is empty, the connection is put back into regular mode.
If you need to send regular commands to Redis while in subscriber mode, just
open another connection with a new client (use `client.duplicate()` to quickly duplicate an existing client).
#### Subscriber Events
If a client has subscriptions active, it may emit these events:
**"message" (channel, message)**:
Client will emit `message` for every message received that matches an active subscription.
Listeners are passed the channel name as `channel` and the message as `message`.
**"pmessage" (pattern, channel, message)**:
Client will emit `pmessage` for every message received that matches an active
subscription pattern. Listeners are passed the original pattern used with
`PSUBSCRIBE` as `pattern`, the sending channel name as `channel`, and the
message as `message`.
**"message_buffer" (channel, message)**:
This is the same as the `message` event with the exception, that it is always
going to emit a buffer. If you listen to the `message` event at the same time as
the `message_buffer`, it is always going to emit a string.
**"pmessage_buffer" (pattern, channel, message)**:
This is the same as the `pmessage` event with the exception, that it is always
going to emit a buffer. If you listen to the `pmessage` event at the same time
as the `pmessage_buffer`, it is always going to emit a string.
**"subscribe" (channel, count)**:
Client will emit `subscribe` in response to a `SUBSCRIBE` command. Listeners are
passed the channel name as `channel` and the new count of subscriptions for this
client as `count`.
**"psubscribe" (pattern, count)**:
Client will emit `psubscribe` in response to a `PSUBSCRIBE` command. Listeners
are passed the original pattern as `pattern`, and the new count of subscriptions
for this client as `count`.
**"unsubscribe" (channel, count)**:
Client will emit `unsubscribe` in response to a `UNSUBSCRIBE` command. Listeners
are passed the channel name as `channel` and the new count of subscriptions for
this client as `count`. When `count` is 0, this client has left subscriber mode
and no more subscriber events will be emitted.
**"punsubscribe" (pattern, count)**:
Client will emit `punsubscribe` in response to a `PUNSUBSCRIBE` command.
Listeners are passed the channel name as `channel` and the new count of
subscriptions for this client as `count`. When `count` is 0, this client has
left subscriber mode and no more subscriber events will be emitted.
### client.multi([commands])
`MULTI` commands are queued up until an `EXEC` is issued, and then all commands
are run atomically by Redis. The interface returns an
individual `Multi` object by calling `client.multi()`. If any command fails to
queue, all commands are rolled back and none is going to be executed (For
further information see the [Redis transactions](http://redis.io/topics/transactions) documentation).
```js
const redis = require("redis");
const client = redis.createClient();
let setSize = 20;
client.sadd("key", "member1");
client.sadd("key", "member2");
while (setSize > 0) {
client.sadd("key", "member" + setSize);
setSize -= 1;
}
// chain commands
client
.multi()
.scard("key")
.smembers("key")
.keys("*")
.dbsize()
.exec(function(err, replies) {
console.log("MULTI got " + replies.length + " replies");
replies.forEach(function(reply, index) {
console.log("REPLY @ index " + index + ": " + reply.toString());
});
});
```
#### Multi.exec([callback])
`client.multi()` is a constructor that returns a `Multi` object. `Multi` objects
share all of the same command methods as `client` objects do. Commands are
queued up inside the `Multi` object until `Multi.exec()` is invoked.
If your code contains an syntax error an `EXECABORT` error is going to be thrown
and all commands are going to be aborted. That error contains a `.errors`
property that contains the concrete errors.
If all commands were queued successfully and an error is thrown by redis while
processing the commands that error is going to be returned in the result array!
No other command is going to be aborted though than the ones failing.
You can either chain together `MULTI` commands as in the above example, or you
can queue individual commands while still sending regular client command as in
this example:
```js
const redis = require("redis");
const client = redis.createClient();
// start a separate multi command queue
const multi = client.multi();
// add some commands to the queue
multi.incr("count_cats", redis.print);
multi.incr("count_dogs", redis.print);
// runs a command immediately outside of the `multi` instance
client.mset("count_cats", 100, "count_dogs", 50, redis.print);
// drains the multi queue and runs each command atomically
multi.exec(function(err, replies) {
console.log(replies); // 101, 51
});
```
In addition to adding commands to the `MULTI` queue individually, you can also
pass an array of commands and arguments to the constructor:
```js
const redis = require("redis");
const client = redis.createClient();
client
.multi([
["mget", "foo", "bar", redis.print],
["incr", "hello"],
])
.exec(function(err, replies) {
console.log(replies);
});
```
#### Multi.exec_atomic([callback])
Identical to Multi.exec but with the difference that executing a single command
will not use transactions.
#### Optimistic Locks
Using `multi` you can make sure your modifications run as a transaction, but you
can't be sure you got there first. What if another client modified a key while
you were working with it's data?
To solve this, Redis supports the [WATCH](https://redis.io/topics/transactions)
command, which is meant to be used with MULTI:
```js
const redis = require("redis");
const client = redis.createClient();
client.watch("foo", function(watchError) {
if (watchError) throw watchError;
client.get("foo", function(getError, result) {
if (getError) throw getError;
// Process result
// Heavy and time consuming operation here to generate "bar"
client
.multi()
.set("foo", "bar")
.exec(function(execError, results) {
/**
* If err is null, it means Redis successfully attempted
* the operation.
*/
if (execError) throw err;
/**
* If results === null, it means that a concurrent client
* changed the key while we were processing it and thus
* the execution of the MULTI command was not performed.
*
* NOTICE: Failing an execution of MULTI is not considered
* an error. So you will have err === null and results === null
*/
});
});
});
```
The above snippet shows the correct usage of `watch` with `multi`. Every time a
watched key is changed before the execution of a `multi` command, the execution
will return `null`. On a normal situation, the execution will return an array of
values with the results of the operations.
As stated in the snippet, failing the execution of a `multi` command being watched
is not considered an error. The execution may return an error if, for example, the
client cannot connect to Redis.
An example where we can see the execution of a `multi` command fail is as follows:
```js
const clients = {
watcher: redis.createClient(),
modifier: redis.createClient(),
};
clients.watcher.watch("foo", function(watchError) {
if (watchError) throw watchError;
// if you comment out the next line, the transaction will work
clients.modifier.set("foo", Math.random(), setError => {
if (setError) throw err;
});
// using a setTimeout here to ensure that the MULTI/EXEC will come after the SET.
// Normally, you would use a callback to ensure order, but I want the above SET command
// to be easily comment-out-able.
setTimeout(function() {
clients.watcher
.multi()
.set("foo", "bar")
.set("hello", "world")
.exec((multiExecError, results) => {
if (multiExecError) throw multiExecError;
if (results === null) {
console.log("transaction aborted because results were null");
} else {
console.log("transaction worked and returned", results);
}
clients.watcher.quit();
clients.modifier.quit();
});
}, 1000);
});
```
#### `WATCH` limitations
Redis WATCH works only on _whole_ key values. For example, with WATCH you can
watch a hash for modifications, but you cannot watch a specific field of a hash.
The following example would watch the keys `foo` and `hello`, not the field `hello`
of hash `foo`:
```js
const redis = require("redis");
const client = redis.createClient();
client.hget("foo", "hello", function(hashGetError, result) {
if (hashGetError) throw hashGetError;
//Do some processing with the value from this field and watch it after
client.watch("foo", "hello", function(watchError) {
if (watchError) throw watchError;
/**
* This is now watching the keys 'foo' and 'hello'. It is not
* watching the field 'hello' of hash 'foo'. Because the key 'foo'
* refers to a hash, this command is now watching the entire hash
* for modifications.
*/
});
});
```
This limitation also applies to sets (you can not watch individual set members)
and any other collections.
### client.batch([commands])
Identical to `.multi()` without transactions. This is recommended if you want to
execute many commands at once but don't need to rely on transactions.
`BATCH` commands are queued up until an `EXEC` is issued, and then all commands
are run atomically by Redis. The interface returns an
individual `Batch` object by calling `client.batch()`. The only difference
between .batch and .multi is that no transaction is going to be used.
Be aware that the errors are - just like in multi statements - in the result.
Otherwise both, errors and results could be returned at the same time.
If you fire many commands at once this is going to boost the execution speed
significantly compared to firing the same commands in a loop without waiting for
the result! See the benchmarks for further comparison. Please remember that all
commands are kept in memory until they are fired.
### Monitor mode
Redis supports the `MONITOR` command, which lets you see all commands received
by the Redis server across all client connections, including from other client
libraries and other computers.
A `monitor` event is going to be emitted for every command fired from any client
connected to the server including the monitoring client itself. The callback for
the `monitor` event takes a timestamp from the Redis server, an array of command
arguments and the raw monitoring string.
#### Example:
```js
const redis = require("redis");
const client = redis.createClient();
client.monitor(function(err, res) {
console.log("Entering monitoring mode.");
});
client.set("foo", "bar");
client.on("monitor", function(time, args, rawReply) {
console.log(time + ": " + args); // 1458910076.446514:['set', 'foo', 'bar']
});
```
## Extras
Some other things you might find useful.
### `client.server_info`
After the ready probe completes, the results from the INFO command are saved in
the `client.server_info` object.
The `versions` key contains an array of the elements of the version string for
easy comparison.
```
> client.server_info.redis_version
'2.3.0'
> client.server_info.versions
[ 2, 3, 0 ]
```
### `redis.print()`
A handy callback function for displaying return values when testing. Example:
```js
const redis = require("redis");
const client = redis.createClient();
client.on("connect", function() {
client.set("foo", "bar", redis.print); // => "Reply: OK"
client.get("foo", redis.print); // => "Reply: bar"
client.quit();
});
```
### Multi-word commands
To execute redis multi-word commands like `SCRIPT LOAD` or `CLIENT LIST` pass
the second word as first parameter:
```js
client.script("load", "return 1");
client
.multi()
.script("load", "return 1")
.exec();
client.multi([["script", "load", "return 1"]]).exec();
```
### `client.duplicate([options][, callback])`
Duplicate all current options and return a new redisClient instance. All options
passed to the duplicate function are going to replace the original option. If
you pass a callback, duplicate is going to wait until the client is ready and
returns it in the callback. If an error occurs in the meanwhile, that is going
to return an error instead in the callback.
One example of when to use duplicate() would be to accommodate the connection-
blocking redis commands `BRPOP`, `BLPOP`, and `BRPOPLPUSH`. If these commands
are used on the same Redis client instance as non-blocking commands, the
non-blocking ones may be queued up until after the blocking ones finish.
Another reason to use duplicate() is when multiple DBs on the same server are
accessed via the redis SELECT command. Each DB could use its own connection.
### `client.send_command(command_name[, [args][, callback]])`
All Redis commands have been added to the `client` object. However, if new
commands are introduced before this library is updated or if you want to add
individual commands you can use `send_command()` to send arbitrary commands to
Redis.
All commands are sent as multi-bulk commands. `args` can either be an Array of
arguments, or omitted / set to undefined.
### `redis.add_command(command_name)`
Calling add_command will add a new command to the prototype. The exact command
name will be used when calling using this new command. Using arbitrary arguments
is possible as with any other command.
### `client.connected`
Boolean tracking the state of the connection to the Redis server.
### `client.command_queue_length`
The number of commands that have been sent to the Redis server but not yet
replied to. You can use this to enforce some kind of maximum queue depth for
commands while connected.
### `client.offline_queue_length`
The number of commands that have been queued up for a future connection. You can
use this to enforce some kind of maximum queue depth for pre-connection
commands.
### Commands with Optional and Keyword arguments
This applies to anything that uses an optional `[WITHSCORES]` or `[LIMIT offset count]` in the [redis.io/commands](http://redis.io/commands) documentation.
#### Example
```js
const args = ["myzset", 1, "one", 2, "two", 3, "three", 99, "ninety-nine"];
client.zadd(args, function(addError, addResponse) {
if (addError) throw addError;
console.log("added " + addResponse + " items.");
// -Infinity and +Infinity also work
const args1 = ["myzset", "+inf", "-inf"];
client.zrevrangebyscore(args1, function(rangeError, rangeResponse) {
if (rangeError) throw rangeError;
console.log("response1", rangeResponse);
// ...
});
const max = 3;
const min = 1;
const offset = 1;
const count = 2;
const args2 = ["myzset", max, min, "WITHSCORES", "LIMIT", offset, count];
client.zrevrangebyscore(args2, function(rangeError, rangeResponse) {
if (rangeError) throw rangeError;
console.log("response2", rangeResponse);
// ...
});
});
```
## Performance
Much effort has been spent to make Node Redis as fast as possible for common operations.
```
Mac mini (2018), i7-3.2GHz and 32gb memory
clients: 1, NodeJS: 12.15.0, Redis: 5.0.6, parser: javascript, connected by: tcp
PING, 1/1 avg/max: 0.03/ 3.28 2501ms total, 31926 ops/sec
PING, batch 50/1 avg/max: 0.08/ 3.35 2501ms total, 599460 ops/sec
SET 4B str, 1/1 avg/max: 0.03/ 3.54 2501ms total, 29483 ops/sec
SET 4B str, batch 50/1 avg/max: 0.10/ 1.39 2501ms total, 477689 ops/sec
SET 4B buf, 1/1 avg/max: 0.04/ 1.52 2501ms total, 23449 ops/sec
SET 4B buf, batch 50/1 avg/max: 0.20/ 2.09 2501ms total, 244382 ops/sec
GET 4B str, 1/1 avg/max: 0.03/ 1.35 2501ms total, 32205 ops/sec
GET 4B str, batch 50/1 avg/max: 0.09/ 2.02 2501ms total, 568992 ops/sec
GET 4B buf, 1/1 avg/max: 0.03/ 2.93 2501ms total, 32802 ops/sec
GET 4B buf, batch 50/1 avg/max: 0.08/ 1.03 2501ms total, 592863 ops/sec
SET 4KiB str, 1/1 avg/max: 0.03/ 0.76 2501ms total, 29287 ops/sec
SET 4KiB str, batch 50/1 avg/max: 0.35/ 2.97 2501ms total, 143163 ops/sec
SET 4KiB buf, 1/1 avg/max: 0.04/ 1.21 2501ms total, 23070 ops/sec
SET 4KiB buf, batch 50/1 avg/max: 0.28/ 2.34 2501ms total, 176809 ops/sec
GET 4KiB str, 1/1 avg/max: 0.03/ 1.54 2501ms total, 29555 ops/sec
GET 4KiB str, batch 50/1 avg/max: 0.18/ 1.59 2501ms total, 279188 ops/sec
GET 4KiB buf, 1/1 avg/max: 0.03/ 1.80 2501ms total, 30681 ops/sec
GET 4KiB buf, batch 50/1 avg/max: 0.17/ 5.00 2501ms total, 285886 ops/sec
INCR, 1/1 avg/max: 0.03/ 1.99 2501ms total, 32757 ops/sec
INCR, batch 50/1 avg/max: 0.09/ 2.54 2501ms total, 538964 ops/sec
LPUSH, 1/1 avg/max: 0.05/ 4.85 2501ms total, 19482 ops/sec
LPUSH, batch 50/1 avg/max: 0.12/ 9.52 2501ms total, 395562 ops/sec
LRANGE 10, 1/1 avg/max: 0.06/ 9.21 2501ms total, 17062 ops/sec
LRANGE 10, batch 50/1 avg/max: 0.22/ 1.03 2501ms total, 228269 ops/sec
LRANGE 100, 1/1 avg/max: 0.05/ 1.44 2501ms total, 19051 ops/sec
LRANGE 100, batch 50/1 avg/max: 0.99/ 3.46 2501ms total, 50480 ops/sec
SET 4MiB str, 1/1 avg/max: 4.11/ 13.96 2501ms total, 243 ops/sec
SET 4MiB str, batch 20/1 avg/max: 91.16/145.01 2553ms total, 219 ops/sec
SET 4MiB buf, 1/1 avg/max: 2.81/ 11.90 2502ms total, 354 ops/sec
SET 4MiB buf, batch 20/1 avg/max: 36.21/ 70.96 2535ms total, 552 ops/sec
GET 4MiB str, 1/1 avg/max: 2.82/ 19.10 2503ms total, 354 ops/sec
GET 4MiB str, batch 20/1 avg/max: 128.57/207.86 2572ms total, 156 ops/sec
GET 4MiB buf, 1/1 avg/max: 3.13/ 23.88 2501ms total, 318 ops/sec
GET 4MiB buf, batch 20/1 avg/max: 65.91/ 87.59 2572ms total, 303 ops/sec
```
## Debugging
To get debug output run your Node Redis application with `NODE_DEBUG=redis`.
This is also going to result in good stack traces opposed to useless ones
otherwise for any async operation.
If you only want to have good stack traces but not the debug output run your
application in development mode instead (`NODE_ENV=development`).
Good stack traces are only activated in development and debug mode as this
results in a significant performance penalty.
**_Comparison_**:
Standard stack trace:
```
ReplyError: ERR wrong number of arguments for 'set' command
at parseError (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:158:12)
at parseType (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:219:14)
```
Debug stack trace:
```
ReplyError: ERR wrong number of arguments for 'set' command
at new Command (/home/ruben/repos/redis/lib/command.js:9:902)
at RedisClient.set (/home/ruben/repos/redis/lib/commands.js:9:3238)
at Context. (/home/ruben/repos/redis/test/good_stacks.spec.js:20:20)
at callFnAsync (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:349:8)
at Test.Runnable.run (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:301:7)
at Runner.runTest (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:422:10)
at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:528:12
at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:342:14)
at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:352:7
at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:284:14)
at Immediate._onImmediate (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:320:5)
at processImmediate [as _immediateCallback] (timers.js:383:17)
```
## Contributing
Please see the [contributing guide](CONTRIBUTING.md).
## License
This repository is licensed under the "MIT" license. See [LICENSE](LICENSE).
node-redis-3.0.2/benchmarks/ 0000775 0000000 0000000 00000000000 13620024551 0015660 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/benchmarks/diff_multi_bench_output.js 0000775 0000000 0000000 00000005770 13620024551 0023133 0 ustar 00root root 0000000 0000000 'use strict';
var fs = require('fs');
var metrics = require('metrics');
// `node diff_multi_bench_output.js beforeBench.txt afterBench.txt`
var file1 = process.argv[2];
var file2 = process.argv[3];
if (!file1 || !file2) {
console.log('Please supply two file arguments:');
var n = __filename;
n = n.substring(n.lastIndexOf('/', n.length));
console.log(' node .' + n + ' benchBefore.txt benchAfter.txt\n');
console.log('To generate the benchmark files, run');
console.log(' npm run benchmark > benchBefore.txt\n');
console.log('Thank you for benchmarking responsibly.');
return;
}
var before_lines = fs.readFileSync(file1, 'utf8').split('\n');
var after_lines = fs.readFileSync(file2, 'utf8').split('\n');
var total_ops = new metrics.Histogram.createUniformHistogram();
console.log('Comparing before,', file1, '(', before_lines.length, 'lines)', 'to after,', file2, '(', after_lines.length, 'lines)');
function is_whitespace (s) {
return !!s.trim();
}
function pad (input, len, chr, right) {
var str = input.toString();
chr = chr || ' ';
if (right) {
while (str.length < len) {
str += chr;
}
} else {
while (str.length < len) {
str = chr + str;
}
}
return str;
}
// green if greater than 0, red otherwise
function humanize_diff (num, unit, toFixed) {
unit = unit || '';
if (num > 0) {
return ' +' + pad(num.toFixed(toFixed || 0) + unit, 7);
}
return ' -' + pad(Math.abs(num).toFixed(toFixed || 0) + unit, 7);
}
function command_name (words) {
var line = words.join(' ');
return line.substr(0, line.indexOf(','));
}
before_lines.forEach(function (b, i) {
var a = after_lines[i];
if (!a || !b || !b.trim() || !a.trim()) {
// console.log('#ignored#', '>'+a+'<', '>'+b+'<');
return;
}
var b_words = b.split(' ').filter(is_whitespace);
var a_words = a.split(' ').filter(is_whitespace);
var ops = [b_words, a_words].map(function (words) {
// console.log(words);
return words.slice(-2, -1) | 0;
}).filter(function (num) {
var isNaN = !num && num !== 0;
return !isNaN;
});
if (ops.length !== 2) {
return;
}
var delta = ops[1] - ops[0];
var pct = +((delta / ops[0]) * 100);
ops[0] = pad(ops[0], 6);
ops[1] = pad(ops[1], 6);
total_ops.update(delta);
delta = humanize_diff(delta);
var small_delta = pct < 3 && pct > -3;
// Let's mark differences above 20% bold
var big_delta = pct > 20 || pct < -20 ? ';1' : '';
pct = humanize_diff(pct, '', 2) + '%';
var str = pad((command_name(a_words) === command_name(b_words) ? command_name(a_words) + ':' : '404:'), 14, false, true) +
(pad(ops.join(' -> '), 15) + ' ops/sec (∆' + delta + pct + ')');
str = (small_delta ? '' : (/-[^>]/.test(str) ? '\x1b[31' : '\x1b[32') + big_delta + 'm') + str + '\x1b[0m';
console.log(str);
});
console.log('Mean difference in ops/sec:', humanize_diff(total_ops.mean(), '', 1));
node-redis-3.0.2/benchmarks/multi_bench.js 0000664 0000000 0000000 00000024615 13620024551 0020517 0 ustar 00root root 0000000 0000000 'use strict';
var path = require('path');
var RedisProcess = require('../test/lib/redis-process');
var rp;
var client_nr = 0;
var redis = require('../index');
var totalTime = 0;
var metrics = require('metrics');
var tests = [];
// var bluebird = require('bluebird');
// bluebird.promisifyAll(redis.RedisClient.prototype);
// bluebird.promisifyAll(redis.Multi.prototype);
function returnArg (name, def) {
var matches = process.argv.filter(function (entry) {
return entry.indexOf(name + '=') === 0;
});
if (matches.length) {
return matches[0].substr(name.length + 1);
}
return def;
}
var num_clients = returnArg('clients', 1);
var run_time = returnArg('time', 2500); // ms
var pipeline = returnArg('pipeline', 1); // number of concurrent commands
var versions_logged = false;
var client_options = {
parser: returnArg('parser', 'javascript'),
path: returnArg('socket') // '/tmp/redis.sock'
};
var small_str, large_str, small_buf, large_buf, very_large_str, very_large_buf;
function lpad (input, len, chr) {
var str = input.toString();
chr = chr || ' ';
while (str.length < len) {
str = chr + str;
}
return str;
}
metrics.Histogram.prototype.print_line = function () {
var obj = this.printObj();
return lpad((obj.mean / 1e6).toFixed(2), 6) + '/' + lpad((obj.max / 1e6).toFixed(2), 6);
};
function Test (args) {
this.args = args;
this.args.pipeline = +pipeline;
this.callback = null;
this.clients = [];
this.clients_ready = 0;
this.commands_sent = 0;
this.commands_completed = 0;
this.max_pipeline = +pipeline;
this.batch_pipeline = this.args.batch || 0;
this.client_options = args.client_options || {};
this.client_options.parser = client_options.parser;
this.client_options.connect_timeout = 1000;
if (client_options.path) {
this.client_options.path = client_options.path;
}
this.connect_latency = new metrics.Histogram();
this.ready_latency = new metrics.Histogram();
this.command_latency = new metrics.Histogram();
}
Test.prototype.run = function (callback) {
var i;
this.callback = callback;
for (i = 0; i < num_clients ; i++) {
this.new_client(i);
}
};
Test.prototype.new_client = function (id) {
var self = this, new_client;
new_client = redis.createClient(this.client_options);
new_client.create_time = Date.now();
new_client.on('connect', function () {
self.connect_latency.update(Date.now() - new_client.create_time);
});
new_client.on('ready', function () {
if (!versions_logged) {
console.log(
'clients: ' + num_clients +
', NodeJS: ' + process.versions.node +
', Redis: ' + new_client.server_info.redis_version +
', parser: ' + client_options.parser +
', connected by: ' + (client_options.path ? 'socket' : 'tcp')
);
versions_logged = true;
}
self.ready_latency.update(Date.now() - new_client.create_time);
self.clients_ready++;
if (self.clients_ready === self.clients.length) {
self.on_clients_ready();
}
});
// If no redis server is running, start one
new_client.on('error', function (err) {
if (err.code === 'CONNECTION_BROKEN') {
throw err;
}
if (rp) {
return;
}
rp = true;
var conf = '../test/conf/redis.conf';
RedisProcess.start(function (err, _rp) {
if (err) {
throw err;
}
rp = _rp;
}, path.resolve(__dirname, conf));
});
self.clients[id] = new_client;
};
Test.prototype.on_clients_ready = function () {
process.stdout.write(lpad(this.args.descr, 13) + ', ' + (this.args.batch ? lpad('batch ' + this.args.batch, 9) : lpad(this.args.pipeline, 9)) + '/' + this.clients_ready + ' ');
this.test_start = Date.now();
this.fill_pipeline();
};
Test.prototype.fill_pipeline = function () {
var pipeline = this.commands_sent - this.commands_completed;
if (this.test_start < Date.now() - run_time) {
if (this.ended) {
return;
}
this.ended = true;
this.print_stats();
this.stop_clients();
return;
}
if (this.batch_pipeline) {
this.batch();
} else {
while (pipeline < this.max_pipeline) {
this.commands_sent++;
pipeline++;
this.send_next();
}
}
};
Test.prototype.batch = function () {
var self = this,
cur_client = client_nr++ % this.clients.length,
start = process.hrtime(),
i = 0,
batch = this.clients[cur_client].batch();
while (i++ < this.batch_pipeline) {
this.commands_sent++;
batch[this.args.command](this.args.args);
}
batch.exec(function (err, res) {
if (err) {
throw err;
}
self.commands_completed += res.length;
self.command_latency.update(process.hrtime(start)[1]);
self.fill_pipeline();
});
};
Test.prototype.stop_clients = function () {
var self = this;
this.clients.forEach(function (client, pos) {
if (pos === self.clients.length - 1) {
client.quit(function (err, res) {
self.callback();
});
} else {
client.quit();
}
});
};
Test.prototype.send_next = function () {
var self = this,
cur_client = this.commands_sent % this.clients.length,
start = process.hrtime();
this.clients[cur_client][this.args.command](this.args.args, function (err, res) {
if (err) {
throw err;
}
self.commands_completed++;
self.command_latency.update(process.hrtime(start)[1]);
self.fill_pipeline();
});
};
Test.prototype.print_stats = function () {
var duration = Date.now() - this.test_start;
totalTime += duration;
console.log('avg/max: ' + this.command_latency.print_line() + lpad(duration, 5) + 'ms total, ' +
lpad(Math.round(this.commands_completed / (duration / 1000)), 7) + ' ops/sec');
};
small_str = '1234';
small_buf = Buffer.from(small_str);
large_str = (new Array(4096 + 1).join('-'));
large_buf = Buffer.from(large_str);
very_large_str = (new Array((4 * 1024 * 1024) + 1).join('-'));
very_large_buf = Buffer.from(very_large_str);
tests.push(new Test({descr: 'PING', command: 'ping', args: []}));
tests.push(new Test({descr: 'PING', command: 'ping', args: [], batch: 50}));
tests.push(new Test({descr: 'SET 4B str', command: 'set', args: ['foo_rand000000000000', small_str]}));
tests.push(new Test({descr: 'SET 4B str', command: 'set', args: ['foo_rand000000000000', small_str], batch: 50}));
tests.push(new Test({descr: 'SET 4B buf', command: 'set', args: ['foo_rand000000000000', small_buf]}));
tests.push(new Test({descr: 'SET 4B buf', command: 'set', args: ['foo_rand000000000000', small_buf], batch: 50}));
tests.push(new Test({descr: 'GET 4B str', command: 'get', args: ['foo_rand000000000000']}));
tests.push(new Test({descr: 'GET 4B str', command: 'get', args: ['foo_rand000000000000'], batch: 50}));
tests.push(new Test({descr: 'GET 4B buf', command: 'get', args: ['foo_rand000000000000'], client_options: { return_buffers: true} }));
tests.push(new Test({descr: 'GET 4B buf', command: 'get', args: ['foo_rand000000000000'], batch: 50, client_options: { return_buffers: true} }));
tests.push(new Test({descr: 'SET 4KiB str', command: 'set', args: ['foo_rand000000000001', large_str]}));
tests.push(new Test({descr: 'SET 4KiB str', command: 'set', args: ['foo_rand000000000001', large_str], batch: 50}));
tests.push(new Test({descr: 'SET 4KiB buf', command: 'set', args: ['foo_rand000000000001', large_buf]}));
tests.push(new Test({descr: 'SET 4KiB buf', command: 'set', args: ['foo_rand000000000001', large_buf], batch: 50}));
tests.push(new Test({descr: 'GET 4KiB str', command: 'get', args: ['foo_rand000000000001']}));
tests.push(new Test({descr: 'GET 4KiB str', command: 'get', args: ['foo_rand000000000001'], batch: 50}));
tests.push(new Test({descr: 'GET 4KiB buf', command: 'get', args: ['foo_rand000000000001'], client_options: { return_buffers: true} }));
tests.push(new Test({descr: 'GET 4KiB buf', command: 'get', args: ['foo_rand000000000001'], batch: 50, client_options: { return_buffers: true} }));
tests.push(new Test({descr: 'INCR', command: 'incr', args: ['counter_rand000000000000']}));
tests.push(new Test({descr: 'INCR', command: 'incr', args: ['counter_rand000000000000'], batch: 50}));
tests.push(new Test({descr: 'LPUSH', command: 'lpush', args: ['mylist', small_str]}));
tests.push(new Test({descr: 'LPUSH', command: 'lpush', args: ['mylist', small_str], batch: 50}));
tests.push(new Test({descr: 'LRANGE 10', command: 'lrange', args: ['mylist', '0', '9']}));
tests.push(new Test({descr: 'LRANGE 10', command: 'lrange', args: ['mylist', '0', '9'], batch: 50}));
tests.push(new Test({descr: 'LRANGE 100', command: 'lrange', args: ['mylist', '0', '99']}));
tests.push(new Test({descr: 'LRANGE 100', command: 'lrange', args: ['mylist', '0', '99'], batch: 50}));
tests.push(new Test({descr: 'SET 4MiB str', command: 'set', args: ['foo_rand000000000002', very_large_str]}));
tests.push(new Test({descr: 'SET 4MiB str', command: 'set', args: ['foo_rand000000000002', very_large_str], batch: 20}));
tests.push(new Test({descr: 'SET 4MiB buf', command: 'set', args: ['foo_rand000000000002', very_large_buf]}));
tests.push(new Test({descr: 'SET 4MiB buf', command: 'set', args: ['foo_rand000000000002', very_large_buf], batch: 20}));
tests.push(new Test({descr: 'GET 4MiB str', command: 'get', args: ['foo_rand000000000002']}));
tests.push(new Test({descr: 'GET 4MiB str', command: 'get', args: ['foo_rand000000000002'], batch: 20}));
tests.push(new Test({descr: 'GET 4MiB buf', command: 'get', args: ['foo_rand000000000002'], client_options: { return_buffers: true} }));
tests.push(new Test({descr: 'GET 4MiB buf', command: 'get', args: ['foo_rand000000000002'], batch: 20, client_options: { return_buffers: true} }));
function next () {
var test = tests.shift();
if (test) {
test.run(function () {
next();
});
} else if (rp) {
// Stop the redis process if started by the benchmark
rp.stop(function () {
rp = undefined;
next();
});
} else {
console.log('End of tests. Total time elapsed:', totalTime, 'ms');
process.exit(0);
}
}
next();
node-redis-3.0.2/examples/ 0000775 0000000 0000000 00000000000 13620024551 0015361 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/examples/auth.js 0000664 0000000 0000000 00000000254 13620024551 0016661 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
// The client stashes the password and will reauthenticate on every connect.
redis.createClient({
password: 'somepass'
});
node-redis-3.0.2/examples/backpressure_drain.js 0000664 0000000 0000000 00000001371 13620024551 0021567 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('../index');
var client = redis.createClient();
var remaining_ops = 100000;
var paused = false;
function op () {
if (remaining_ops <= 0) {
console.error('Finished.');
process.exit(0);
}
remaining_ops--;
client.hset('test hash', 'val ' + remaining_ops, remaining_ops);
if (client.should_buffer === true) {
console.log('Pausing at ' + remaining_ops);
paused = true;
} else {
setTimeout(op, 1);
}
}
client.on('drain', function () {
if (paused) {
console.log('Resuming at ' + remaining_ops);
paused = false;
process.nextTick(op);
} else {
console.log('Got drain while not paused at ' + remaining_ops);
}
});
op();
node-redis-3.0.2/examples/eval.js 0000664 0000000 0000000 00000000443 13620024551 0016647 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('../index');
var client = redis.createClient();
client.eval('return 100.5', 0, function (err, res) {
console.dir(err);
console.dir(res);
});
client.eval([ 'return 100.5', 0 ], function (err, res) {
console.dir(err);
console.dir(res);
});
node-redis-3.0.2/examples/extend.js 0000664 0000000 0000000 00000001253 13620024551 0017207 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient();
// Extend the RedisClient prototype to add a custom method
// This one converts the results from 'INFO' into a JavaScript Object
redis.RedisClient.prototype.parse_info = function (callback) {
this.info(function (err, res) {
var lines = res.toString().split('\r\n').sort();
var obj = {};
lines.forEach(function (line) {
var parts = line.split(':');
if (parts[1]) {
obj[parts[0]] = parts[1];
}
});
callback(obj);
});
};
client.parse_info(function (info) {
console.dir(info);
client.quit();
});
node-redis-3.0.2/examples/file.js 0000664 0000000 0000000 00000002512 13620024551 0016636 0 ustar 00root root 0000000 0000000 'use strict';
// Read a file from disk, store it in Redis, then read it back from Redis.
var redis = require('redis');
var client = redis.createClient({
return_buffers: true
});
var fs = require('fs');
var assert = require('assert');
var filename = 'grumpyCat.jpg';
// Get the file I use for testing like this:
// curl http://media4.popsugar-assets.com/files/2014/08/08/878/n/1922507/caef16ec354ca23b_thumb_temp_cover_file32304521407524949.xxxlarge/i/Funny-Cat-GIFs.jpg -o grumpyCat.jpg
// or just use your own file.
// Read a file from fs, store it in Redis, get it back from Redis, write it back to fs.
fs.readFile(filename, function (err, data) {
if (err) throw err;
console.log('Read ' + data.length + ' bytes from filesystem.');
client.set(filename, data, redis.print); // set entire file
client.get(filename, function (err, reply) { // get entire file
if (err) {
console.log('Get error: ' + err);
} else {
assert.strictEqual(data.inspect(), reply.inspect());
fs.writeFile('duplicate_' + filename, reply, function (err) {
if (err) {
console.log('Error on write: ' + err);
} else {
console.log('File written.');
}
client.end();
});
}
});
});
node-redis-3.0.2/examples/mget.js 0000664 0000000 0000000 00000000253 13620024551 0016653 0 ustar 00root root 0000000 0000000 'use strict';
var client = require('redis').createClient();
client.mget(['sessions started', 'sessions started', 'foo'], function (err, res) {
console.dir(res);
});
node-redis-3.0.2/examples/monitor.js 0000664 0000000 0000000 00000000431 13620024551 0017404 0 ustar 00root root 0000000 0000000 'use strict';
var client = require('../index').createClient();
var util = require('util');
client.monitor(function (err, res) {
console.log('Entering monitoring mode.');
});
client.on('monitor', function (time, args) {
console.log(time + ': ' + util.inspect(args));
});
node-redis-3.0.2/examples/multi.js 0000664 0000000 0000000 00000002374 13620024551 0017057 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient();
var set_size = 20;
client.sadd('bigset', 'a member');
client.sadd('bigset', 'another member');
while (set_size > 0) {
client.sadd('bigset', 'member ' + set_size);
set_size -= 1;
}
// multi chain with an individual callback
client.multi()
.scard('bigset')
.smembers('bigset')
.keys('*', function (err, replies) {
client.mget(replies, redis.print);
})
.dbsize()
.exec(function (err, replies) {
console.log('MULTI got ' + replies.length + ' replies');
replies.forEach(function (reply, index) {
console.log('Reply ' + index + ': ' + reply.toString());
});
});
client.mset('incr thing', 100, 'incr other thing', 1, redis.print);
// start a separate multi command queue
var multi = client.multi();
multi.incr('incr thing', redis.print);
multi.incr('incr other thing', redis.print);
// runs immediately
client.get('incr thing', redis.print); // 100
// drains multi queue and runs atomically
multi.exec(function (err, replies) {
console.log(replies); // 101, 2
});
// you can re-run the same transaction if you like
multi.exec(function (err, replies) {
console.log(replies); // 102, 3
client.quit();
});
node-redis-3.0.2/examples/multi2.js 0000664 0000000 0000000 00000001423 13620024551 0017133 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient();
// start a separate command queue for multi
var multi = client.multi();
multi.incr('incr thing', redis.print);
multi.incr('incr other thing', redis.print);
// runs immediately
client.mset('incr thing', 100, 'incr other thing', 1, redis.print);
// drains multi queue and runs atomically
multi.exec(function (err, replies) {
console.log(replies); // 101, 2
});
// you can re-run the same transaction if you like
multi.exec(function (err, replies) {
console.log(replies); // 102, 3
client.quit();
});
client.multi([
['mget', 'multifoo', 'multibar', redis.print],
['incr', 'multifoo'],
['incr', 'multibar']
]).exec(function (err, replies) {
console.log(replies.toString());
});
node-redis-3.0.2/examples/psubscribe.js 0000664 0000000 0000000 00000001766 13620024551 0020072 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client1 = redis.createClient();
var client2 = redis.createClient();
var client3 = redis.createClient();
var client4 = redis.createClient();
var msg_count = 0;
client1.on('psubscribe', function (pattern, count) {
console.log('client1 psubscribed to ' + pattern + ', ' + count + ' total subscriptions');
client2.publish('channeltwo', 'Me!');
client3.publish('channelthree', 'Me too!');
client4.publish('channelfour', 'And me too!');
});
client1.on('punsubscribe', function (pattern, count) {
console.log('client1 punsubscribed from ' + pattern + ', ' + count + ' total subscriptions');
client4.end();
client3.end();
client2.end();
client1.end();
});
client1.on('pmessage', function (pattern, channel, message) {
console.log('(' + pattern + ') client1 received message on ' + channel + ': ' + message);
msg_count += 1;
if (msg_count === 3) {
client1.punsubscribe();
}
});
client1.psubscribe('channel*');
node-redis-3.0.2/examples/pub_sub.js 0000664 0000000 0000000 00000002457 13620024551 0017366 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client1 = redis.createClient();
var msg_count = 0;
var client2 = redis.createClient();
// Most clients probably don't do much on 'subscribe'. This example uses it to coordinate things within one program.
client1.on('subscribe', function (channel, count) {
console.log('client1 subscribed to ' + channel + ', ' + count + ' total subscriptions');
if (count === 2) {
client2.publish('a nice channel', 'I am sending a message.');
client2.publish('another one', 'I am sending a second message.');
client2.publish('a nice channel', 'I am sending my last message.');
}
});
client1.on('unsubscribe', function (channel, count) {
console.log('client1 unsubscribed from ' + channel + ', ' + count + ' total subscriptions');
if (count === 0) {
client2.end();
client1.end();
}
});
client1.on('message', function (channel, message) {
console.log('client1 channel ' + channel + ': ' + message);
msg_count += 1;
if (msg_count === 3) {
client1.unsubscribe();
}
});
client1.on('ready', function () {
// if you need auth, do it here
client1.incr('did a thing');
client1.subscribe('a nice channel', 'another one');
});
client2.on('ready', function () {
// if you need auth, do it here
});
node-redis-3.0.2/examples/scan.js 0000664 0000000 0000000 00000003440 13620024551 0016644 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient();
var cursor = '0';
function scan () {
client.scan(
cursor,
'MATCH', 'q:job:*',
'COUNT', '10',
function (err, res) {
if (err) throw err;
// Update the cursor position for the next scan
cursor = res[0];
// get the SCAN result for this iteration
var keys = res[1];
// Remember: more or less than COUNT or no keys may be returned
// See http://redis.io/commands/scan#the-count-option
// Also, SCAN may return the same key multiple times
// See http://redis.io/commands/scan#scan-guarantees
// Additionally, you should always have the code that uses the keys
// before the code checking the cursor.
if (keys.length > 0) {
console.log('Array of matching keys', keys);
}
// It's important to note that the cursor and returned keys
// vary independently. The scan is never complete until redis
// returns a non-zero cursor. However, with MATCH and large
// collections, most iterations will return an empty keys array.
// Still, a cursor of zero DOES NOT mean that there are no keys.
// A zero cursor just means that the SCAN is complete, but there
// might be one last batch of results to process.
// From :
// 'An iteration starts when the cursor is set to 0,
// and terminates when the cursor returned by the server is 0.'
if (cursor === '0') {
return console.log('Iteration complete');
}
return scan();
}
);
}
scan();
node-redis-3.0.2/examples/simple.js 0000664 0000000 0000000 00000001373 13620024551 0017214 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient();
client.on('error', function (err) {
console.log('error event - ' + client.host + ':' + client.port + ' - ' + err);
});
client.set('string key', 'string val', redis.print);
client.hset('hash key', 'hashtest 1', 'some value', redis.print);
client.hset(['hash key', 'hashtest 2', 'some other value'], redis.print);
client.hkeys('hash key', function (err, replies) {
if (err) {
return console.error('error response - ' + err);
}
console.log(replies.length + ' replies:');
replies.forEach(function (reply, i) {
console.log(' ' + i + ': ' + reply);
});
});
client.quit(function (err, res) {
console.log('Exiting from quit command.');
});
node-redis-3.0.2/examples/sort.js 0000664 0000000 0000000 00000000670 13620024551 0016711 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient();
client.sadd('mylist', 1);
client.sadd('mylist', 2);
client.sadd('mylist', 3);
client.set('weight_1', 5);
client.set('weight_2', 500);
client.set('weight_3', 1);
client.set('object_1', 'foo');
client.set('object_2', 'bar');
client.set('object_3', 'qux');
client.sort('mylist', 'by', 'weight_*', 'get', 'object_*', redis.print);
// Prints Reply: qux,foo,bar
node-redis-3.0.2/examples/streams.js 0000664 0000000 0000000 00000002373 13620024551 0017402 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client1 = redis.createClient();
var client2 = redis.createClient();
var client3 = redis.createClient();
client1.xadd('mystream', '*', 'field1', 'm1', function (err) {
if (err) {
return console.error(err);
}
client1.xgroup('CREATE', 'mystream', 'mygroup', '$', function (err) {
if (err) {
return console.error(err);
}
});
client2.xreadgroup('GROUP', 'mygroup', 'consumer', 'Block', 1000, 'NOACK',
'STREAMS', 'mystream', '>', function (err, stream) {
if (err) {
return console.error(err);
}
console.log('client2 ' + stream);
});
client3.xreadgroup('GROUP', 'mygroup', 'consumer', 'Block', 1000, 'NOACK',
'STREAMS', 'mystream', '>', function (err, stream) {
if (err) {
return console.error(err);
}
console.log('client3 ' + stream);
});
client1.xadd('mystream', '*', 'field1', 'm2', function (err) {
if (err) {
return console.error(err);
}
});
client1.xadd('mystream', '*', 'field1', 'm3', function (err) {
if (err) {
return console.error(err);
}
});
});
node-redis-3.0.2/examples/subqueries.js 0000664 0000000 0000000 00000000727 13620024551 0020114 0 ustar 00root root 0000000 0000000 'use strict';
// Sending commands in response to other commands.
// This example runs 'type' against every key in the database
//
var client = require('redis').createClient();
client.keys('*', function (err, keys) {
keys.forEach(function (key, pos) {
client.type(key, function (err, keytype) {
console.log(key + ' is ' + keytype);
if (pos === (keys.length - 1)) {
client.quit();
}
});
});
});
node-redis-3.0.2/examples/subquery.js 0000664 0000000 0000000 00000000757 13620024551 0017607 0 ustar 00root root 0000000 0000000 'use strict';
var client = require('redis').createClient();
// build a map of all keys and their types
client.keys('*', function (err, all_keys) {
var key_types = {};
all_keys.forEach(function (key, pos) { // use second arg of forEach to get pos
client.type(key, function (err, type) {
key_types[key] = type;
if (pos === all_keys.length - 1) { // callbacks all run in order
console.dir(key_types);
}
});
});
});
node-redis-3.0.2/examples/unix_socket.js 0000664 0000000 0000000 00000001213 13620024551 0020247 0 ustar 00root root 0000000 0000000 'use strict';
var redis = require('redis');
var client = redis.createClient('/tmp/redis.sock');
var profiler = require('v8-profiler');
client.on('connect', function () {
console.log('Got Unix socket connection.');
});
client.on('error', function (err) {
console.log(err.message);
});
client.set('space chars', 'space value');
setInterval(function () {
client.get('space chars');
}, 100);
function done () {
client.info(function (err, reply) {
console.log(reply.toString());
client.quit();
});
}
setTimeout(function () {
console.log('Taking snapshot.');
profiler.takeSnapshot();
done();
}, 5000);
node-redis-3.0.2/examples/web_server.js 0000664 0000000 0000000 00000002277 13620024551 0020072 0 ustar 00root root 0000000 0000000 'use strict';
// A simple web server that generates dyanmic content based on responses from Redis
var http = require('http');
var redis_client = require('redis').createClient();
http.createServer(function (request, response) { // The server
response.writeHead(200, {
'Content-Type': 'text/plain'
});
var redis_info, total_requests;
redis_client.info(function (err, reply) {
redis_info = reply; // stash response in outer scope
});
redis_client.incr('requests', function (err, reply) {
total_requests = reply; // stash response in outer scope
});
redis_client.hincrby('ip', request.connection.remoteAddress, 1);
redis_client.hgetall('ip', function (err, reply) {
// This is the last reply, so all of the previous replies must have completed already
response.write('This page was generated after talking to redis.\n\n' +
'Redis info:\n' + redis_info + '\n' +
'Total requests: ' + total_requests + '\n\n' +
'IP count: \n');
Object.keys(reply).forEach(function (ip) {
response.write(' ' + ip + ': ' + reply[ip] + '\n');
});
response.end();
});
}).listen(80);
node-redis-3.0.2/index.js 0000664 0000000 0000000 00000115322 13620024551 0015214 0 ustar 00root root 0000000 0000000 'use strict';
var net = require('net');
var tls = require('tls');
var util = require('util');
var utils = require('./lib/utils');
var Command = require('./lib/command');
var Queue = require('denque');
var errorClasses = require('./lib/customErrors');
var EventEmitter = require('events');
var Parser = require('redis-parser');
var RedisErrors = require('redis-errors');
var commands = require('redis-commands');
var debug = require('./lib/debug');
var unifyOptions = require('./lib/createClient');
var SUBSCRIBE_COMMANDS = {
subscribe: true,
unsubscribe: true,
psubscribe: true,
punsubscribe: true
};
function noop () {}
function handle_detect_buffers_reply (reply, command, buffer_args) {
if (buffer_args === false || this.message_buffers) {
// If detect_buffers option was specified, then the reply from the parser will be a buffer.
// If this command did not use Buffer arguments, then convert the reply to Strings here.
reply = utils.reply_to_strings(reply);
}
if (command === 'hgetall') {
reply = utils.reply_to_object(reply);
}
return reply;
}
exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
// Attention: The second parameter might be removed at will and is not officially supported.
// Do not rely on this
function RedisClient (options, stream) {
// Copy the options so they are not mutated
options = utils.clone(options);
EventEmitter.call(this);
var cnx_options = {};
var self = this;
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
for (var tls_option in options.tls) {
cnx_options[tls_option] = options.tls[tls_option];
// Copy the tls options into the general options to make sure the address is set right
if (tls_option === 'port' || tls_option === 'host' || tls_option === 'path' || tls_option === 'family') {
options[tls_option] = options.tls[tls_option];
}
}
if (stream) {
// The stream from the outside is used so no connection from this side is triggered but from the server this client should talk to
// Reconnect etc won't work with this. This requires monkey patching to work, so it is not officially supported
options.stream = stream;
this.address = '"Private stream"';
} else if (options.path) {
cnx_options.path = options.path;
this.address = options.path;
} else {
cnx_options.port = +options.port || 6379;
cnx_options.host = options.host || '127.0.0.1';
cnx_options.family = (!options.family && net.isIP(cnx_options.host)) || (options.family === 'IPv6' ? 6 : 4);
this.address = cnx_options.host + ':' + cnx_options.port;
}
this.connection_options = cnx_options;
this.connection_id = RedisClient.connection_id++;
this.connected = false;
this.ready = false;
if (options.socket_keepalive === undefined) {
options.socket_keepalive = true;
}
if (options.socket_initial_delay === undefined) {
options.socket_initial_delay = 0;
// set default to 0, which is aligned to https://nodejs.org/api/net.html#net_socket_setkeepalive_enable_initialdelay
}
for (var command in options.rename_commands) {
options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
}
options.return_buffers = !!options.return_buffers;
options.detect_buffers = !!options.detect_buffers;
// Override the detect_buffers setting if return_buffers is active and print a warning
if (options.return_buffers && options.detect_buffers) {
self.warn('WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
options.detect_buffers = false;
}
if (options.detect_buffers) {
// We only need to look at the arguments if we do not know what we have to return
this.handle_reply = handle_detect_buffers_reply;
}
this.should_buffer = false;
this.command_queue = new Queue(); // Holds sent commands to de-pipeline them
this.offline_queue = new Queue(); // Holds commands issued but not able to be sent
this.pipeline_queue = new Queue(); // Holds all pipelined commands
// ATTENTION: connect_timeout should change in v.3.0 so it does not count towards ending reconnection attempts after x seconds
// This should be done by the retry_strategy. Instead it should only be the timeout for connecting to redis
this.connect_timeout = +options.connect_timeout || 3600000; // 60 * 60 * 1000 ms
this.enable_offline_queue = options.enable_offline_queue === false ? false : true;
this.initialize_retry_vars();
this.pub_sub_mode = 0;
this.subscription_set = {};
this.monitoring = false;
this.message_buffers = false;
this.closing = false;
this.server_info = {};
this.auth_pass = options.auth_pass || options.password;
this.selected_db = options.db; // Save the selected db here, used when reconnecting
this.fire_strings = true; // Determine if strings or buffers should be written to the stream
this.pipeline = false;
this.sub_commands_left = 0;
this.times_connected = 0;
this.buffers = options.return_buffers || options.detect_buffers;
this.options = options;
this.reply = 'ON'; // Returning replies is the default
this.create_stream();
// The listeners will not be attached right away, so let's print the deprecation message while the listener is attached
this.on('newListener', function (event) {
if ((event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer') && !this.buffers && !this.message_buffers) {
this.reply_parser.optionReturnBuffers = true;
this.message_buffers = true;
this.handle_reply = handle_detect_buffers_reply;
}
});
}
util.inherits(RedisClient, EventEmitter);
RedisClient.connection_id = 0;
function create_parser (self) {
return new Parser({
returnReply: function (data) {
self.return_reply(data);
},
returnError: function (err) {
// Return a ReplyError to indicate Redis returned an error
self.return_error(err);
},
returnFatalError: function (err) {
// Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
// Note: the execution order is important. First flush and emit, then create the stream
err.message += '. Please report this.';
self.ready = false;
self.flush_and_error({
message: 'Fatal error encountered. Command aborted.',
code: 'NR_FATAL'
}, {
error: err,
queues: ['command_queue']
});
self.emit('error', err);
self.create_stream();
},
returnBuffers: self.buffers || self.message_buffers,
stringNumbers: self.options.string_numbers || false
});
}
/******************************************************************************
All functions in here are internal besides the RedisClient constructor
and the exported functions. Don't rely on them as they will be private
functions in node_redis v.3
******************************************************************************/
// Attention: the function name "create_stream" should not be changed, as other libraries need this to mock the stream (e.g. fakeredis)
RedisClient.prototype.create_stream = function () {
var self = this;
// Init parser
this.reply_parser = create_parser(this);
if (this.options.stream) {
// Only add the listeners once in case of a reconnect try (that won't work)
if (this.stream) {
return;
}
this.stream = this.options.stream;
} else {
// On a reconnect destroy the former stream and retry
if (this.stream) {
this.stream.removeAllListeners();
this.stream.destroy();
}
/* istanbul ignore if: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
if (this.options.tls) {
this.stream = tls.connect(this.connection_options);
} else {
this.stream = net.createConnection(this.connection_options);
}
}
if (this.options.connect_timeout) {
this.stream.setTimeout(this.connect_timeout, function () {
// Note: This is only tested if a internet connection is established
self.retry_totaltime = self.connect_timeout;
self.connection_gone('timeout');
});
}
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
var connect_event = this.options.tls ? 'secureConnect' : 'connect';
this.stream.once(connect_event, function () {
this.removeAllListeners('timeout');
self.times_connected++;
self.on_connect();
});
this.stream.on('data', function (buffer_from_socket) {
// The buffer_from_socket.toString() has a significant impact on big chunks and therefore this should only be used if necessary
debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString());
self.reply_parser.execute(buffer_from_socket);
});
this.stream.on('error', function (err) {
self.on_error(err);
});
this.stream.once('close', function (hadError) {
self.connection_gone('close');
});
this.stream.once('end', function () {
self.connection_gone('end');
});
this.stream.on('drain', function () {
self.drain();
});
this.stream.setNoDelay();
// Fire the command before redis is connected to be sure it's the first fired command
if (this.auth_pass !== undefined) {
this.ready = true;
// Fail silently as we might not be able to connect
this.auth(this.auth_pass, function (err) {
if (err && err.code !== 'UNCERTAIN_STATE') {
self.emit('error', err);
}
});
this.ready = false;
}
};
RedisClient.prototype.handle_reply = function (reply, command) {
if (command === 'hgetall') {
reply = utils.reply_to_object(reply);
}
return reply;
};
RedisClient.prototype.cork = noop;
RedisClient.prototype.uncork = noop;
RedisClient.prototype.initialize_retry_vars = function () {
this.retry_timer = null;
this.retry_totaltime = 0;
this.retry_delay = 200;
this.retry_backoff = 1.7;
this.attempts = 1;
};
RedisClient.prototype.warn = function (msg) {
var self = this;
// Warn on the next tick. Otherwise no event listener can be added
// for warnings that are emitted in the redis client constructor
process.nextTick(function () {
if (self.listeners('warning').length !== 0) {
self.emit('warning', msg);
} else {
console.warn('node_redis:', msg);
}
});
};
// Flush provided queues, erroring any items with a callback first
RedisClient.prototype.flush_and_error = function (error_attributes, options) {
options = options || {};
var aggregated_errors = [];
var queue_names = options.queues || ['command_queue', 'offline_queue']; // Flush the command_queue first to keep the order intakt
for (var i = 0; i < queue_names.length; i++) {
// If the command was fired it might have been processed so far
if (queue_names[i] === 'command_queue') {
error_attributes.message += ' It might have been processed.';
} else { // As the command_queue is flushed first, remove this for the offline queue
error_attributes.message = error_attributes.message.replace(' It might have been processed.', '');
}
// Don't flush everything from the queue
for (var command_obj = this[queue_names[i]].shift(); command_obj; command_obj = this[queue_names[i]].shift()) {
var err = new errorClasses.AbortError(error_attributes);
if (command_obj.error) {
err.stack = err.stack + command_obj.error.stack.replace(/^Error.*?\n/, '\n');
}
err.command = command_obj.command.toUpperCase();
if (command_obj.args && command_obj.args.length) {
err.args = command_obj.args;
}
if (options.error) {
err.origin = options.error;
}
if (typeof command_obj.callback === 'function') {
command_obj.callback(err);
} else {
aggregated_errors.push(err);
}
}
}
// Currently this would be a breaking change, therefore it's only emitted in debug_mode
if (exports.debug_mode && aggregated_errors.length) {
var error;
if (aggregated_errors.length === 1) {
error = aggregated_errors[0];
} else {
error_attributes.message = error_attributes.message.replace('It', 'They').replace(/command/i, '$&s');
error = new errorClasses.AggregateError(error_attributes);
error.errors = aggregated_errors;
}
this.emit('error', error);
}
};
RedisClient.prototype.on_error = function (err) {
if (this.closing) {
return;
}
err.message = 'Redis connection to ' + this.address + ' failed - ' + err.message;
debug(err.message);
this.connected = false;
this.ready = false;
// Only emit the error if the retry_strategy option is not set
if (!this.options.retry_strategy) {
this.emit('error', err);
}
// 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
// then we should try to reconnect.
this.connection_gone('error', err);
};
RedisClient.prototype.on_connect = function () {
debug('Stream connected ' + this.address + ' id ' + this.connection_id);
this.connected = true;
this.ready = false;
this.emitted_end = false;
this.stream.setKeepAlive(this.options.socket_keepalive, this.options.socket_initial_delay);
this.stream.setTimeout(0);
this.emit('connect');
this.initialize_retry_vars();
if (this.options.no_ready_check) {
this.on_ready();
} else {
this.ready_check();
}
};
RedisClient.prototype.on_ready = function () {
var self = this;
debug('on_ready called ' + this.address + ' id ' + this.connection_id);
this.ready = true;
this.cork = function () {
self.pipeline = true;
if (self.stream.cork) {
self.stream.cork();
}
};
this.uncork = function () {
if (self.fire_strings) {
self.write_strings();
} else {
self.write_buffers();
}
self.pipeline = false;
self.fire_strings = true;
if (self.stream.uncork) {
// TODO: Consider using next tick here. See https://github.com/NodeRedis/node_redis/issues/1033
self.stream.uncork();
}
};
// Restore modal commands from previous connection. The order of the commands is important
if (this.selected_db !== undefined) {
this.internal_send_command(new Command('select', [this.selected_db]));
}
if (this.monitoring) { // Monitor has to be fired before pub sub commands
this.internal_send_command(new Command('monitor', []));
}
var callback_count = Object.keys(this.subscription_set).length;
if (!this.options.disable_resubscribing && callback_count) {
// only emit 'ready' when all subscriptions were made again
// TODO: Remove the countdown for ready here. This is not coherent with all other modes and should therefore not be handled special
// We know we are ready as soon as all commands were fired
var callback = function () {
callback_count--;
if (callback_count === 0) {
self.emit('ready');
}
};
debug('Sending pub/sub on_ready commands');
for (var key in this.subscription_set) {
var command = key.slice(0, key.indexOf('_'));
var args = this.subscription_set[key];
this[command]([args], callback);
}
this.send_offline_queue();
return;
}
this.send_offline_queue();
this.emit('ready');
};
RedisClient.prototype.on_info_cmd = function (err, res) {
if (err) {
if (err.message === "ERR unknown command 'info'") {
this.on_ready();
return;
}
err.message = 'Ready check failed: ' + err.message;
this.emit('error', err);
return;
}
/* istanbul ignore if: some servers might not respond with any info data. This is just a safety check that is difficult to test */
if (!res) {
debug('The info command returned without any data.');
this.on_ready();
return;
}
if (!this.server_info.loading || this.server_info.loading === '0') {
// If the master_link_status exists but the link is not up, try again after 50 ms
if (this.server_info.master_link_status && this.server_info.master_link_status !== 'up') {
this.server_info.loading_eta_seconds = 0.05;
} else {
// Eta loading should change
debug('Redis server ready.');
this.on_ready();
return;
}
}
var retry_time = +this.server_info.loading_eta_seconds * 1000;
if (retry_time > 1000) {
retry_time = 1000;
}
debug('Redis server still loading, trying again in ' + retry_time);
setTimeout(function (self) {
self.ready_check();
}, retry_time, this);
};
RedisClient.prototype.ready_check = function () {
var self = this;
debug('Checking server ready state...');
// Always fire this info command as first command even if other commands are already queued up
this.ready = true;
this.info(function (err, res) {
self.on_info_cmd(err, res);
});
this.ready = false;
};
RedisClient.prototype.send_offline_queue = function () {
for (var command_obj = this.offline_queue.shift(); command_obj; command_obj = this.offline_queue.shift()) {
debug('Sending offline command: ' + command_obj.command);
this.internal_send_command(command_obj);
}
this.drain();
};
var retry_connection = function (self, error) {
debug('Retrying connection...');
var reconnect_params = {
delay: self.retry_delay,
attempt: self.attempts,
error: error
};
if (self.options.camel_case) {
reconnect_params.totalRetryTime = self.retry_totaltime;
reconnect_params.timesConnected = self.times_connected;
} else {
reconnect_params.total_retry_time = self.retry_totaltime;
reconnect_params.times_connected = self.times_connected;
}
self.emit('reconnecting', reconnect_params);
self.retry_totaltime += self.retry_delay;
self.attempts += 1;
self.retry_delay = Math.round(self.retry_delay * self.retry_backoff);
self.create_stream();
self.retry_timer = null;
};
RedisClient.prototype.connection_gone = function (why, error) {
// If a retry is already in progress, just let that happen
if (this.retry_timer) {
return;
}
error = error || null;
debug('Redis connection is gone from ' + why + ' event.');
this.connected = false;
this.ready = false;
// Deactivate cork to work with the offline queue
this.cork = noop;
this.uncork = noop;
this.pipeline = false;
this.pub_sub_mode = 0;
// since we are collapsing end and close, users don't expect to be called twice
if (!this.emitted_end) {
this.emit('end');
this.emitted_end = true;
}
// If this is a requested shutdown, then don't retry
if (this.closing) {
debug('Connection ended by quit / end command, not retrying.');
this.flush_and_error({
message: 'Stream connection ended and command aborted.',
code: 'NR_CLOSED'
}, {
error: error
});
return;
}
if (typeof this.options.retry_strategy === 'function') {
var retry_params = {
attempt: this.attempts,
error: error
};
if (this.options.camel_case) {
retry_params.totalRetryTime = this.retry_totaltime;
retry_params.timesConnected = this.times_connected;
} else {
retry_params.total_retry_time = this.retry_totaltime;
retry_params.times_connected = this.times_connected;
}
this.retry_delay = this.options.retry_strategy(retry_params);
if (typeof this.retry_delay !== 'number') {
// Pass individual error through
if (this.retry_delay instanceof Error) {
error = this.retry_delay;
}
var errorMessage = 'Redis connection in broken state: retry aborted.';
this.flush_and_error({
message: errorMessage,
code: 'CONNECTION_BROKEN',
}, {
error: error
});
var retryError = new Error(errorMessage);
retryError.code = 'CONNECTION_BROKEN';
if (error) {
retryError.origin = error;
}
this.end(false);
this.emit('error', retryError);
return;
}
}
if (this.retry_totaltime >= this.connect_timeout) {
var message = 'Redis connection in broken state: connection timeout exceeded.';
this.flush_and_error({
message: message,
code: 'CONNECTION_BROKEN',
}, {
error: error
});
var err = new Error(message);
err.code = 'CONNECTION_BROKEN';
if (error) {
err.origin = error;
}
this.end(false);
this.emit('error', err);
return;
}
// Retry commands after a reconnect instead of throwing an error. Use this with caution
if (this.options.retry_unfulfilled_commands) {
this.offline_queue.unshift.apply(this.offline_queue, this.command_queue.toArray());
this.command_queue.clear();
} else if (this.command_queue.length !== 0) {
this.flush_and_error({
message: 'Redis connection lost and command aborted.',
code: 'UNCERTAIN_STATE'
}, {
error: error,
queues: ['command_queue']
});
}
if (this.retry_totaltime + this.retry_delay > this.connect_timeout) {
// Do not exceed the maximum
this.retry_delay = this.connect_timeout - this.retry_totaltime;
}
debug('Retry connection in ' + this.retry_delay + ' ms');
this.retry_timer = setTimeout(retry_connection, this.retry_delay, this, error);
};
RedisClient.prototype.return_error = function (err) {
var command_obj = this.command_queue.shift();
if (command_obj.error) {
err.stack = command_obj.error.stack.replace(/^Error.*?\n/, 'ReplyError: ' + err.message + '\n');
}
err.command = command_obj.command.toUpperCase();
if (command_obj.args && command_obj.args.length) {
err.args = command_obj.args;
}
// Count down pub sub mode if in entering modus
if (this.pub_sub_mode > 1) {
this.pub_sub_mode--;
}
var match = err.message.match(utils.err_code);
// LUA script could return user errors that don't behave like all other errors!
if (match) {
err.code = match[1];
}
utils.callback_or_emit(this, command_obj.callback, err);
};
RedisClient.prototype.drain = function () {
this.should_buffer = false;
};
function normal_reply (self, reply) {
var command_obj = self.command_queue.shift();
if (typeof command_obj.callback === 'function') {
if (command_obj.command !== 'exec') {
reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
}
command_obj.callback(null, reply);
} else {
debug('No callback for reply');
}
}
function subscribe_unsubscribe (self, reply, type) {
// Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
// The pub sub commands return each argument in a separate return value and have to be handled that way
var command_obj = self.command_queue.get(0);
var buffer = self.options.return_buffers || self.options.detect_buffers && command_obj.buffer_args;
var channel = (buffer || reply[1] === null) ? reply[1] : reply[1].toString();
var count = +reply[2]; // Return the channel counter as number no matter if `string_numbers` is activated or not
debug(type, channel);
// Emit first, then return the callback
if (channel !== null) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from
self.emit(type, channel, count);
if (type === 'subscribe' || type === 'psubscribe') {
self.subscription_set[type + '_' + channel] = channel;
} else {
type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe'; // Make types consistent
delete self.subscription_set[type + '_' + channel];
}
}
if (command_obj.args.length === 1 || self.sub_commands_left === 1 || command_obj.args.length === 0 && (count === 0 || channel === null)) {
if (count === 0) { // unsubscribed from all channels
var running_command;
var i = 1;
self.pub_sub_mode = 0; // Deactivating pub sub mode
// This should be a rare case and therefore handling it this way should be good performance wise for the general case
while (running_command = self.command_queue.get(i)) {
if (SUBSCRIBE_COMMANDS[running_command.command]) {
self.pub_sub_mode = i; // Entering pub sub mode again
break;
}
i++;
}
}
self.command_queue.shift();
if (typeof command_obj.callback === 'function') {
// TODO: The current return value is pretty useless.
// Evaluate to change this in v.4 to return all subscribed / unsubscribed channels in an array including the number of channels subscribed too
command_obj.callback(null, channel);
}
self.sub_commands_left = 0;
} else {
if (self.sub_commands_left !== 0) {
self.sub_commands_left--;
} else {
self.sub_commands_left = command_obj.args.length ? command_obj.args.length - 1 : count;
}
}
}
function return_pub_sub (self, reply) {
var type = reply[0].toString();
if (type === 'message') { // channel, message
if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.4 to always return a string on the normal emitter
self.emit('message', reply[1].toString(), reply[2].toString());
self.emit('message_buffer', reply[1], reply[2]);
self.emit('messageBuffer', reply[1], reply[2]);
} else {
self.emit('message', reply[1], reply[2]);
}
} else if (type === 'pmessage') { // pattern, channel, message
if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.4 to always return a string on the normal emitter
self.emit('pmessage', reply[1].toString(), reply[2].toString(), reply[3].toString());
self.emit('pmessage_buffer', reply[1], reply[2], reply[3]);
self.emit('pmessageBuffer', reply[1], reply[2], reply[3]);
} else {
self.emit('pmessage', reply[1], reply[2], reply[3]);
}
} else {
subscribe_unsubscribe(self, reply, type);
}
}
RedisClient.prototype.return_reply = function (reply) {
if (this.monitoring) {
var replyStr;
if (this.buffers && Buffer.isBuffer(reply)) {
replyStr = reply.toString();
} else {
replyStr = reply;
}
// If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
if (typeof replyStr === 'string' && utils.monitor_regex.test(replyStr)) {
var timestamp = replyStr.slice(0, replyStr.indexOf(' '));
var args = replyStr.slice(replyStr.indexOf('"') + 1, -1).split('" "').map(function (elem) {
return elem.replace(/\\"/g, '"');
});
this.emit('monitor', timestamp, args, replyStr);
return;
}
}
if (this.pub_sub_mode === 0) {
normal_reply(this, reply);
} else if (this.pub_sub_mode !== 1) {
this.pub_sub_mode--;
normal_reply(this, reply);
} else if (!(reply instanceof Array) || reply.length <= 2) {
// Only PING and QUIT are allowed in this context besides the pub sub commands
// Ping replies with ['pong', null|value] and quit with 'OK'
normal_reply(this, reply);
} else {
return_pub_sub(this, reply);
}
};
function handle_offline_command (self, command_obj) {
var command = command_obj.command;
var err, msg;
if (self.closing || !self.enable_offline_queue) {
command = command.toUpperCase();
if (!self.closing) {
if (self.stream.writable) {
msg = 'The connection is not yet established and the offline queue is deactivated.';
} else {
msg = 'Stream not writeable.';
}
} else {
msg = 'The connection is already closed.';
}
err = new errorClasses.AbortError({
message: command + " can't be processed. " + msg,
code: 'NR_CLOSED',
command: command
});
if (command_obj.args.length) {
err.args = command_obj.args;
}
utils.reply_in_order(self, command_obj.callback, err);
} else {
debug('Queueing ' + command + ' for next server connection.');
self.offline_queue.push(command_obj);
}
self.should_buffer = true;
}
// Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
// e.g. monitor / info does not work with internal_send_command only
RedisClient.prototype.internal_send_command = function (command_obj) {
var arg, prefix_keys;
var i = 0;
var command_str = '';
var args = command_obj.args;
var command = command_obj.command;
var len = args.length;
var big_data = false;
var args_copy = new Array(len);
if (process.domain && command_obj.callback) {
command_obj.callback = process.domain.bind(command_obj.callback);
}
if (this.ready === false || this.stream.writable === false) {
// Handle offline commands right away
handle_offline_command(this, command_obj);
return false; // Indicate buffering
}
for (i = 0; i < len; i += 1) {
if (typeof args[i] === 'string') {
// 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
if (args[i].length > 30000) {
big_data = true;
args_copy[i] = Buffer.from(args[i], 'utf8');
} else {
args_copy[i] = args[i];
}
} else if (typeof args[i] === 'object') { // Checking for object instead of Buffer.isBuffer helps us finding data types that we can't handle properly
if (args[i] instanceof Date) { // Accept dates as valid input
args_copy[i] = args[i].toString();
} else if (Buffer.isBuffer(args[i])) {
args_copy[i] = args[i];
command_obj.buffer_args = true;
big_data = true;
} else {
var invalidArgError = new Error(
'node_redis: The ' + command.toUpperCase() + ' command contains a invalid argument type.\n' +
'Only strings, dates and buffers are accepted. Please update your code to use valid argument types.'
);
invalidArgError.command = command_obj.command.toUpperCase();
if (command_obj.args && command_obj.args.length) {
invalidArgError.args = command_obj.args;
}
if (command_obj.callback) {
command_obj.callback(invalidArgError);
return false;
}
throw invalidArgError;
}
} else if (typeof args[i] === 'undefined') {
var undefinedArgError = new Error(
'node_redis: The ' + command.toUpperCase() + ' command contains a invalid argument type of "undefined".\n' +
'Only strings, dates and buffers are accepted. Please update your code to use valid argument types.'
);
undefinedArgError.command = command_obj.command.toUpperCase();
if (command_obj.args && command_obj.args.length) {
undefinedArgError.args = command_obj.args;
}
// there is always a callback in this scenario
command_obj.callback(undefinedArgError);
return false;
} else {
// Seems like numbers are converted fast using string concatenation
args_copy[i] = '' + args[i];
}
}
if (this.options.prefix) {
prefix_keys = commands.getKeyIndexes(command, args_copy);
for (i = prefix_keys.pop(); i !== undefined; i = prefix_keys.pop()) {
args_copy[i] = this.options.prefix + args_copy[i];
}
}
if (this.options.rename_commands && this.options.rename_commands[command]) {
command = this.options.rename_commands[command];
}
// Always use 'Multi bulk commands', but if passed any Buffer args, then do multiple writes, one for each arg.
// This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
command_str = '*' + (len + 1) + '\r\n$' + command.length + '\r\n' + command + '\r\n';
if (big_data === false) { // Build up a string and send entire command in one write
for (i = 0; i < len; i += 1) {
arg = args_copy[i];
command_str += '$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n';
}
debug('Send ' + this.address + ' id ' + this.connection_id + ': ' + command_str);
this.write(command_str);
} else {
debug('Send command (' + command_str + ') has Buffer arguments');
this.fire_strings = false;
this.write(command_str);
for (i = 0; i < len; i += 1) {
arg = args_copy[i];
if (typeof arg === 'string') {
this.write('$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n');
} else { // buffer
this.write('$' + arg.length + '\r\n');
this.write(arg);
this.write('\r\n');
}
debug('send_command: buffer send ' + arg.length + ' bytes');
}
}
if (command_obj.call_on_write) {
command_obj.call_on_write();
}
// Handle `CLIENT REPLY ON|OFF|SKIP`
// This has to be checked after call_on_write
/* istanbul ignore else: TODO: Remove this as soon as we test Redis 3.2 on travis */
if (this.reply === 'ON') {
this.command_queue.push(command_obj);
} else {
// Do not expect a reply
// Does this work in combination with the pub sub mode?
if (command_obj.callback) {
utils.reply_in_order(this, command_obj.callback, null, undefined, this.command_queue);
}
if (this.reply === 'SKIP') {
this.reply = 'SKIP_ONE_MORE';
} else if (this.reply === 'SKIP_ONE_MORE') {
this.reply = 'ON';
}
}
return !this.should_buffer;
};
RedisClient.prototype.write_strings = function () {
var str = '';
for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
// Write to stream if the string is bigger than 4mb. The biggest string may be Math.pow(2, 28) - 15 chars long
if (str.length + command.length > 4 * 1024 * 1024) {
this.should_buffer = !this.stream.write(str);
str = '';
}
str += command;
}
if (str !== '') {
this.should_buffer = !this.stream.write(str);
}
};
RedisClient.prototype.write_buffers = function () {
for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
this.should_buffer = !this.stream.write(command);
}
};
RedisClient.prototype.write = function (data) {
if (this.pipeline === false) {
this.should_buffer = !this.stream.write(data);
return;
}
this.pipeline_queue.push(data);
};
Object.defineProperty(exports, 'debugMode', {
get: function () {
return this.debug_mode;
},
set: function (val) {
this.debug_mode = val;
}
});
// Don't officially expose the command_queue directly but only the length as read only variable
Object.defineProperty(RedisClient.prototype, 'command_queue_length', {
get: function () {
return this.command_queue.length;
}
});
Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
get: function () {
return this.offline_queue.length;
}
});
// Add support for camelCase by adding read only properties to the client
// All known exposed snake_case variables are added here
Object.defineProperty(RedisClient.prototype, 'retryDelay', {
get: function () {
return this.retry_delay;
}
});
Object.defineProperty(RedisClient.prototype, 'retryBackoff', {
get: function () {
return this.retry_backoff;
}
});
Object.defineProperty(RedisClient.prototype, 'commandQueueLength', {
get: function () {
return this.command_queue.length;
}
});
Object.defineProperty(RedisClient.prototype, 'offlineQueueLength', {
get: function () {
return this.offline_queue.length;
}
});
Object.defineProperty(RedisClient.prototype, 'shouldBuffer', {
get: function () {
return this.should_buffer;
}
});
Object.defineProperty(RedisClient.prototype, 'connectionId', {
get: function () {
return this.connection_id;
}
});
Object.defineProperty(RedisClient.prototype, 'serverInfo', {
get: function () {
return this.server_info;
}
});
exports.createClient = function () {
return new RedisClient(unifyOptions.apply(null, arguments));
};
exports.RedisClient = RedisClient;
exports.print = utils.print;
exports.Multi = require('./lib/multi');
exports.AbortError = errorClasses.AbortError;
exports.RedisError = RedisErrors.RedisError;
exports.ParserError = RedisErrors.ParserError;
exports.ReplyError = RedisErrors.ReplyError;
exports.AggregateError = errorClasses.AggregateError;
// Add all redis commands / node_redis api to the client
require('./lib/individualCommands');
require('./lib/extendedApi');
//enables adding new commands (for modules and new commands)
exports.addCommand = exports.add_command = require('./lib/commands');
node-redis-3.0.2/lib/ 0000775 0000000 0000000 00000000000 13620024551 0014311 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/lib/command.js 0000664 0000000 0000000 00000000664 13620024551 0016273 0 ustar 00root root 0000000 0000000 'use strict';
var betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG);
function Command (command, args, callback, call_on_write) {
this.command = command;
this.args = args;
this.buffer_args = false;
this.callback = callback;
this.call_on_write = call_on_write;
if (betterStackTraces) {
this.error = new Error();
}
}
module.exports = Command;
node-redis-3.0.2/lib/commands.js 0000664 0000000 0000000 00000007766 13620024551 0016470 0 ustar 00root root 0000000 0000000 'use strict';
var commands = require('redis-commands');
var Multi = require('./multi');
var RedisClient = require('../').RedisClient;
var Command = require('./command');
var addCommand = function (command) {
// Some rare Redis commands use special characters in their command name
// Convert those to a underscore to prevent using invalid function names
var commandName = command.replace(/(?:^([0-9])|[^a-zA-Z0-9_$])/g, '_$1');
// Do not override existing functions
if (!RedisClient.prototype[command]) {
RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function () {
var arr;
var len = arguments.length;
var callback;
var i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
if (len === 2) {
callback = arguments[1];
}
} else if (len > 1 && Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2];
}
len = arguments[1].length;
arr = new Array(len + 1);
arr[0] = arguments[0];
for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i];
}
} else {
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
return this.internal_send_command(new Command(command, arr, callback));
};
// Alias special function names (e.g. NR.RUN becomes NR_RUN and nr_run)
if (commandName !== command) {
RedisClient.prototype[commandName.toUpperCase()] = RedisClient.prototype[commandName] = RedisClient.prototype[command];
}
Object.defineProperty(RedisClient.prototype[command], 'name', {
value: commandName
});
}
// Do not override existing functions
if (!Multi.prototype[command]) {
Multi.prototype[command.toUpperCase()] = Multi.prototype[command] = function () {
var arr;
var len = arguments.length;
var callback;
var i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
if (len === 2) {
callback = arguments[1];
}
} else if (len > 1 && Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2];
}
len = arguments[1].length;
arr = new Array(len + 1);
arr[0] = arguments[0];
for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i];
}
} else {
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
this.queue.push(new Command(command, arr, callback));
return this;
};
// Alias special function names (e.g. NR.RUN becomes NR_RUN and nr_run)
if (commandName !== command) {
Multi.prototype[commandName.toUpperCase()] = Multi.prototype[commandName] = Multi.prototype[command];
}
Object.defineProperty(Multi.prototype[command], 'name', {
value: commandName
});
}
};
commands.list.forEach(addCommand);
module.exports = addCommand;
node-redis-3.0.2/lib/createClient.js 0000664 0000000 0000000 00000006414 13620024551 0017256 0 ustar 00root root 0000000 0000000 'use strict';
var utils = require('./utils');
var URL = require('url');
module.exports = function createClient (port_arg, host_arg, options) {
if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) {
var host;
if (typeof host_arg === 'string') {
host = host_arg;
} else {
if (options && host_arg) {
throw new TypeError('Unknown type of connection in createClient()');
}
options = options || host_arg;
}
options = utils.clone(options);
options.host = host || options.host;
options.port = port_arg;
} else if (typeof port_arg === 'string' || port_arg && port_arg.url) {
options = utils.clone(port_arg.url ? port_arg : host_arg || options);
var url = port_arg.url || port_arg;
var parsed = URL.parse(url, true, true);
// [redis:]//[[user][:password]@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]
if (parsed.slashes) { // We require slashes
if (parsed.auth) {
options.password = parsed.auth.slice(parsed.auth.indexOf(':') + 1);
}
if (parsed.protocol) {
if (parsed.protocol === 'rediss:') {
options.tls = options.tls || {};
} else if (parsed.protocol !== 'redis:') {
console.warn('node_redis: WARNING: You passed "' + parsed.protocol.substring(0, parsed.protocol.length - 1) + '" as protocol instead of the "redis" protocol!');
}
}
if (parsed.pathname && parsed.pathname !== '/') {
options.db = parsed.pathname.substr(1);
}
if (parsed.hostname) {
options.host = parsed.hostname;
}
if (parsed.port) {
options.port = parsed.port;
}
if (parsed.search !== '') {
var elem;
for (elem in parsed.query) {
// If options are passed twice, only the parsed options will be used
if (elem in options) {
if (options[elem] === parsed.query[elem]) {
console.warn('node_redis: WARNING: You passed the ' + elem + ' option twice!');
} else {
throw new RangeError('The ' + elem + ' option is added twice and does not match');
}
}
options[elem] = parsed.query[elem];
}
}
} else if (parsed.hostname) {
throw new RangeError('The redis url must begin with slashes "//" or contain slashes after the redis protocol');
} else {
options.path = url;
}
} else if (typeof port_arg === 'object' || port_arg === undefined) {
options = utils.clone(port_arg || options);
options.host = options.host || host_arg;
if (port_arg && arguments.length !== 1) {
throw new TypeError('Too many arguments passed to createClient. Please only pass the options object');
}
}
if (!options) {
throw new TypeError('Unknown type of connection in createClient()');
}
return options;
};
node-redis-3.0.2/lib/customErrors.js 0000664 0000000 0000000 00000003177 13620024551 0017366 0 ustar 00root root 0000000 0000000 'use strict';
var util = require('util');
var assert = require('assert');
var RedisError = require('redis-errors').RedisError;
var ADD_STACKTRACE = false;
function AbortError (obj, stack) {
assert(obj, 'The options argument is required');
assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object');
Object.defineProperty(this, 'message', {
value: obj.message || '',
configurable: true,
writable: true
});
if (stack || stack === undefined) {
Error.captureStackTrace(this, AbortError);
}
for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
this[key] = obj[key];
}
}
function AggregateError (obj) {
assert(obj, 'The options argument is required');
assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object');
AbortError.call(this, obj, ADD_STACKTRACE);
Object.defineProperty(this, 'message', {
value: obj.message || '',
configurable: true,
writable: true
});
Error.captureStackTrace(this, AggregateError);
for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
this[key] = obj[key];
}
}
util.inherits(AbortError, RedisError);
util.inherits(AggregateError, AbortError);
Object.defineProperty(AbortError.prototype, 'name', {
value: 'AbortError',
configurable: true,
writable: true
});
Object.defineProperty(AggregateError.prototype, 'name', {
value: 'AggregateError',
configurable: true,
writable: true
});
module.exports = {
AbortError: AbortError,
AggregateError: AggregateError
};
node-redis-3.0.2/lib/debug.js 0000664 0000000 0000000 00000000420 13620024551 0015731 0 ustar 00root root 0000000 0000000 'use strict';
var index = require('../');
function debug () {
if (index.debug_mode) {
var data = Array.prototype.slice.call(arguments);
data.unshift(new Date().toISOString());
console.error.apply(null, data);
}
}
module.exports = debug;
node-redis-3.0.2/lib/extendedApi.js 0000664 0000000 0000000 00000010352 13620024551 0017102 0 ustar 00root root 0000000 0000000 'use strict';
var utils = require('./utils');
var debug = require('./debug');
var RedisClient = require('../').RedisClient;
var Command = require('./command');
var noop = function () {};
/**********************************************
All documented and exposed API belongs in here
**********************************************/
// Redirect calls to the appropriate function and use to send arbitrary / not supported commands
RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = function (command, args, callback) {
// Throw to fail early instead of relying in order in this case
if (typeof command !== 'string') {
throw new TypeError('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name');
}
command = command.toLowerCase();
if (!Array.isArray(args)) {
if (args === undefined || args === null) {
args = [];
} else if (typeof args === 'function' && callback === undefined) {
callback = args;
args = [];
} else {
throw new TypeError('Wrong input type "' + args.constructor.name + '" for args');
}
}
if (typeof callback !== 'function' && callback !== undefined) {
throw new TypeError('Wrong input type "' + (callback !== null ? callback.constructor.name : 'null') + '" for callback function');
}
// Using the raw multi command is only possible with this function
// If the command is not yet added to the client, the internal function should be called right away
// Otherwise we need to redirect the calls to make sure the internal functions don't get skipped
// The internal functions could actually be used for any non hooked function
// but this might change from time to time and at the moment there's no good way to distinguish them
// from each other, so let's just do it do it this way for the time being
if (command === 'multi' || typeof this[command] !== 'function') {
return this.internal_send_command(new Command(command, args, callback));
}
if (typeof callback === 'function') {
args = args.concat([callback]); // Prevent manipulating the input array
}
return this[command].apply(this, args);
};
RedisClient.prototype.end = function (flush) {
// Flush queue if wanted
if (flush) {
this.flush_and_error({
message: 'Connection forcefully ended and command aborted.',
code: 'NR_CLOSED'
});
} else if (arguments.length === 0) {
this.warn(
'Using .end() without the flush parameter is deprecated and throws from v.3.0.0 on.\n' +
'Please check the doku (https://github.com/NodeRedis/node_redis) and explictly use flush.'
);
}
// Clear retry_timer
if (this.retry_timer) {
clearTimeout(this.retry_timer);
this.retry_timer = null;
}
this.stream.removeAllListeners();
this.stream.on('error', noop);
this.connected = false;
this.ready = false;
this.closing = true;
return this.stream.destroySoon();
};
RedisClient.prototype.unref = function () {
if (this.connected) {
debug("Unref'ing the socket connection");
this.stream.unref();
} else {
debug('Not connected yet, will unref later');
this.once('connect', function () {
this.unref();
});
}
};
RedisClient.prototype.duplicate = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
var existing_options = utils.clone(this.options);
options = utils.clone(options);
for (var elem in options) {
existing_options[elem] = options[elem];
}
var client = new RedisClient(existing_options);
client.selected_db = options.db || this.selected_db;
if (typeof callback === 'function') {
var ready_listener = function () {
callback(null, client);
client.removeAllListeners(error_listener);
};
var error_listener = function (err) {
callback(err);
client.end(true);
};
client.once('ready', ready_listener);
client.once('error', error_listener);
return;
}
return client;
};
node-redis-3.0.2/lib/individualCommands.js 0000664 0000000 0000000 00000054511 13620024551 0020467 0 ustar 00root root 0000000 0000000 'use strict';
var utils = require('./utils');
var debug = require('./debug');
var Multi = require('./multi');
var Command = require('./command');
var no_password_is_set = /no password is set/;
var loading = /LOADING/;
var RedisClient = require('../').RedisClient;
/********************************************************************************************
Replace built-in redis functions
The callback may be hooked as needed. The same does not apply to the rest of the function.
State should not be set outside of the callback if not absolutly necessary.
This is important to make sure it works the same as single command or in a multi context.
To make sure everything works with the offline queue use the "call_on_write" function.
This is going to be executed while writing to the stream.
TODO: Implement individal command generation as soon as possible to prevent divergent code
on single and multi calls!
********************************************************************************************/
RedisClient.prototype.multi = RedisClient.prototype.MULTI = function multi (args) {
var multi = new Multi(this, args);
multi.exec = multi.EXEC = multi.exec_transaction;
return multi;
};
// ATTENTION: This is not a native function but is still handled as a individual command as it behaves just the same as multi
RedisClient.prototype.batch = RedisClient.prototype.BATCH = function batch (args) {
return new Multi(this, args);
};
function select_callback (self, db, callback) {
return function (err, res) {
if (err === null) {
// Store db in this.select_db to restore it on reconnect
self.selected_db = db;
}
utils.callback_or_emit(self, callback, err, res);
};
}
RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) {
return this.internal_send_command(new Command('select', [db], select_callback(this, db, callback)));
};
Multi.prototype.select = Multi.prototype.SELECT = function select (db, callback) {
this.queue.push(new Command('select', [db], select_callback(this._client, db, callback)));
return this;
};
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor (callback) {
// Use a individual command, as this is a special case that does not has to be checked for any other command
var self = this;
var call_on_write = function () {
// Activating monitor mode has to happen before Redis returned the callback. The monitor result is returned first.
// Therefore we expect the command to be properly processed. If this is not the case, it's not an issue either.
self.monitoring = true;
};
return this.internal_send_command(new Command('monitor', [], callback, call_on_write));
};
// Only works with batch, not in a transaction
Multi.prototype.monitor = Multi.prototype.MONITOR = function monitor (callback) {
// Use a individual command, as this is a special case that does not has to be checked for any other command
if (this.exec !== this.exec_transaction) {
var self = this;
var call_on_write = function () {
self._client.monitoring = true;
};
this.queue.push(new Command('monitor', [], callback, call_on_write));
return this;
}
// Set multi monitoring to indicate the exec that it should abort
// Remove this "hack" as soon as Redis might fix this
this.monitoring = true;
return this;
};
function quit_callback (self, callback) {
return function (err, res) {
if (err && err.code === 'NR_CLOSED') {
// Pretent the quit command worked properly in this case.
// Either the quit landed in the offline queue and was flushed at the reconnect
// or the offline queue is deactivated and the command was rejected right away
// or the stream is not writable
// or while sending the quit, the connection ended / closed
err = null;
res = 'OK';
}
utils.callback_or_emit(self, callback, err, res);
if (self.stream.writable) {
// If the socket is still alive, kill it. This could happen if quit got a NR_CLOSED error code
self.stream.destroy();
}
};
}
RedisClient.prototype.QUIT = RedisClient.prototype.quit = function quit (callback) {
// TODO: Consider this for v.3
// Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue.
// this.ready = this.offline_queue.length === 0;
var backpressure_indicator = this.internal_send_command(new Command('quit', [], quit_callback(this, callback)));
// Calling quit should always end the connection, no matter if there's a connection or not
this.closing = true;
this.ready = false;
return backpressure_indicator;
};
// Only works with batch, not in a transaction
Multi.prototype.QUIT = Multi.prototype.quit = function quit (callback) {
var self = this._client;
var call_on_write = function () {
// If called in a multi context, we expect redis is available
self.closing = true;
self.ready = false;
};
this.queue.push(new Command('quit', [], quit_callback(self, callback), call_on_write));
return this;
};
function info_callback (self, callback) {
return function (err, res) {
if (res) {
var obj = {};
var lines = res.toString().split('\r\n');
var line, parts, sub_parts;
for (var i = 0; i < lines.length; i++) {
parts = lines[i].split(':');
if (parts[1]) {
if (parts[0].indexOf('db') === 0) {
sub_parts = parts[1].split(',');
obj[parts[0]] = {};
while (line = sub_parts.pop()) {
line = line.split('=');
obj[parts[0]][line[0]] = +line[1];
}
} else {
obj[parts[0]] = parts[1];
}
}
}
obj.versions = [];
if (obj.redis_version) {
obj.redis_version.split('.').forEach(function (num) {
obj.versions.push(+num);
});
}
// Expose info key/vals to users
self.server_info = obj;
} else {
self.server_info = {};
}
utils.callback_or_emit(self, callback, err, res);
};
}
// Store info in this.server_info after each call
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) {
var args = [];
if (typeof section === 'function') {
callback = section;
} else if (section !== undefined) {
args = Array.isArray(section) ? section : [section];
}
return this.internal_send_command(new Command('info', args, info_callback(this, callback)));
};
Multi.prototype.info = Multi.prototype.INFO = function info (section, callback) {
var args = [];
if (typeof section === 'function') {
callback = section;
} else if (section !== undefined) {
args = Array.isArray(section) ? section : [section];
}
this.queue.push(new Command('info', args, info_callback(this._client, callback)));
return this;
};
function auth_callback (self, pass, callback) {
return function (err, res) {
if (err) {
if (no_password_is_set.test(err.message)) {
self.warn('Warning: Redis server does not require a password, but a password was supplied.');
err = null;
res = 'OK';
} else if (loading.test(err.message)) {
// If redis is still loading the db, it will not authenticate and everything else will fail
debug('Redis still loading, trying to authenticate later');
setTimeout(function () {
self.auth(pass, callback);
}, 100);
return;
}
}
utils.callback_or_emit(self, callback, err, res);
};
}
RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, callback) {
debug('Sending auth to ' + this.address + ' id ' + this.connection_id);
// Stash auth for connect and reconnect.
this.auth_pass = pass;
var ready = this.ready;
this.ready = ready || this.offline_queue.length === 0;
var tmp = this.internal_send_command(new Command('auth', [pass], auth_callback(this, pass, callback)));
this.ready = ready;
return tmp;
};
// Only works with batch, not in a transaction
Multi.prototype.auth = Multi.prototype.AUTH = function auth (pass, callback) {
debug('Sending auth to ' + this.address + ' id ' + this.connection_id);
// Stash auth for connect and reconnect.
this.auth_pass = pass;
this.queue.push(new Command('auth', [pass], auth_callback(this._client, callback)));
return this;
};
RedisClient.prototype.client = RedisClient.prototype.CLIENT = function client () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2];
}
len = arguments[1].length;
arr = new Array(len + 1);
arr[0] = arguments[0];
for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i];
}
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this;
var call_on_write = undefined;
// CLIENT REPLY ON|OFF|SKIP
/* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
var reply_on_off = arr[1].toString().toUpperCase();
if (reply_on_off === 'ON' || reply_on_off === 'OFF' || reply_on_off === 'SKIP') {
call_on_write = function () {
self.reply = reply_on_off;
};
}
}
return this.internal_send_command(new Command('client', arr, callback, call_on_write));
};
Multi.prototype.client = Multi.prototype.CLIENT = function client () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2];
}
len = arguments[1].length;
arr = new Array(len + 1);
arr[0] = arguments[0];
for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i];
}
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this._client;
var call_on_write = undefined;
// CLIENT REPLY ON|OFF|SKIP
/* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
var reply_on_off = arr[1].toString().toUpperCase();
if (reply_on_off === 'ON' || reply_on_off === 'OFF' || reply_on_off === 'SKIP') {
call_on_write = function () {
self.reply = reply_on_off;
};
}
}
this.queue.push(new Command('client', arr, callback, call_on_write));
return this;
};
RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2];
}
len = arguments[1].length;
arr = new Array(len + 1);
arr[0] = arguments[0];
for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i];
}
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) {
arr = [arguments[0]];
for (var field in arguments[1]) {
arr.push(field, arguments[1][field]);
}
callback = arguments[2];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
return this.internal_send_command(new Command('hmset', arr, callback));
};
Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0];
callback = arguments[1];
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2];
}
len = arguments[1].length;
arr = new Array(len + 1);
arr[0] = arguments[0];
for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i];
}
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) {
arr = [arguments[0]];
for (var field in arguments[1]) {
arr.push(field, arguments[1][field]);
}
callback = arguments[2];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
this.queue.push(new Command('hmset', arr, callback));
return this;
};
RedisClient.prototype.subscribe = RedisClient.prototype.SUBSCRIBE = function subscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
return this.internal_send_command(new Command('subscribe', arr, callback, call_on_write));
};
Multi.prototype.subscribe = Multi.prototype.SUBSCRIBE = function subscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this._client;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
this.queue.push(new Command('subscribe', arr, callback, call_on_write));
return this;
};
RedisClient.prototype.unsubscribe = RedisClient.prototype.UNSUBSCRIBE = function unsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this;
var call_on_write = function () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
return this.internal_send_command(new Command('unsubscribe', arr, callback, call_on_write));
};
Multi.prototype.unsubscribe = Multi.prototype.UNSUBSCRIBE = function unsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this._client;
var call_on_write = function () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
this.queue.push(new Command('unsubscribe', arr, callback, call_on_write));
return this;
};
RedisClient.prototype.psubscribe = RedisClient.prototype.PSUBSCRIBE = function psubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
return this.internal_send_command(new Command('psubscribe', arr, callback, call_on_write));
};
Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this._client;
var call_on_write = function () {
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
this.queue.push(new Command('psubscribe', arr, callback, call_on_write));
return this;
};
RedisClient.prototype.punsubscribe = RedisClient.prototype.PUNSUBSCRIBE = function punsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this;
var call_on_write = function () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
return this.internal_send_command(new Command('punsubscribe', arr, callback, call_on_write));
};
Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscribe () {
var arr,
len = arguments.length,
callback,
i = 0;
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0);
callback = arguments[1];
} else {
len = arguments.length;
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--;
callback = arguments[len];
}
arr = new Array(len);
for (; i < len; i += 1) {
arr[i] = arguments[i];
}
}
var self = this._client;
var call_on_write = function () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
};
this.queue.push(new Command('punsubscribe', arr, callback, call_on_write));
return this;
};
node-redis-3.0.2/lib/multi.js 0000664 0000000 0000000 00000014563 13620024551 0016012 0 ustar 00root root 0000000 0000000 'use strict';
var Queue = require('denque');
var utils = require('./utils');
var Command = require('./command');
function Multi (client, args) {
this._client = client;
this.queue = new Queue();
var command, tmp_args;
if (args) { // Either undefined or an array. Fail hard if it's not an array
for (var i = 0; i < args.length; i++) {
command = args[i][0];
tmp_args = args[i].slice(1);
if (Array.isArray(command)) {
this[command[0]].apply(this, command.slice(1).concat(tmp_args));
} else {
this[command].apply(this, tmp_args);
}
}
}
}
function pipeline_transaction_command (self, command_obj, index) {
// Queueing is done first, then the commands are executed
var tmp = command_obj.callback;
command_obj.callback = function (err, reply) {
// Ignore the multi command. This is applied by node_redis and the user does not benefit by it
if (err && index !== -1) {
if (tmp) {
tmp(err);
}
err.position = index;
self.errors.push(err);
}
// Keep track of who wants buffer responses:
// By the time the callback is called the command_obj got the buffer_args attribute attached
self.wants_buffers[index] = command_obj.buffer_args;
command_obj.callback = tmp;
};
self._client.internal_send_command(command_obj);
}
Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.execAtomic = function exec_atomic (callback) {
if (this.queue.length < 2) {
return this.exec_batch(callback);
}
return this.exec(callback);
};
function multi_callback (self, err, replies) {
var i = 0, command_obj;
if (err) {
err.errors = self.errors;
if (self.callback) {
self.callback(err);
// Exclude connection errors so that those errors won't be emitted twice
} else if (err.code !== 'CONNECTION_BROKEN') {
self._client.emit('error', err);
}
return;
}
if (replies) {
while (command_obj = self.queue.shift()) {
if (replies[i] instanceof Error) {
var match = replies[i].message.match(utils.err_code);
// LUA script could return user errors that don't behave like all other errors!
if (match) {
replies[i].code = match[1];
}
replies[i].command = command_obj.command.toUpperCase();
if (typeof command_obj.callback === 'function') {
command_obj.callback(replies[i]);
}
} else {
// If we asked for strings, even in detect_buffers mode, then return strings:
replies[i] = self._client.handle_reply(replies[i], command_obj.command, self.wants_buffers[i]);
if (typeof command_obj.callback === 'function') {
command_obj.callback(null, replies[i]);
}
}
i++;
}
}
if (self.callback) {
self.callback(null, replies);
}
}
Multi.prototype.exec_transaction = function exec_transaction (callback) {
if (this.monitoring || this._client.monitoring) {
var err = new RangeError(
'Using transaction with a client that is in monitor mode does not work due to faulty return values of Redis.'
);
err.command = 'EXEC';
err.code = 'EXECABORT';
return utils.reply_in_order(this._client, callback, err);
}
var self = this;
var len = self.queue.length;
self.errors = [];
self.callback = callback;
self._client.cork();
self.wants_buffers = new Array(len);
pipeline_transaction_command(self, new Command('multi', []), -1);
// Drain queue, callback will catch 'QUEUED' or error
for (var index = 0; index < len; index++) {
// The commands may not be shifted off, since they are needed in the result handler
pipeline_transaction_command(self, self.queue.get(index), index);
}
self._client.internal_send_command(new Command('exec', [], function (err, replies) {
multi_callback(self, err, replies);
}));
self._client.uncork();
return !self._client.should_buffer;
};
function batch_callback (self, cb, i) {
return function batch_callback (err, res) {
if (err) {
self.results[i] = err;
// Add the position to the error
self.results[i].position = i;
} else {
self.results[i] = res;
}
cb(err, res);
};
}
Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = function exec_batch (callback) {
var self = this;
var len = self.queue.length;
var index = 0;
var command_obj;
if (len === 0) {
utils.reply_in_order(self._client, callback, null, []);
return !self._client.should_buffer;
}
self._client.cork();
if (!callback) {
while (command_obj = self.queue.shift()) {
self._client.internal_send_command(command_obj);
}
self._client.uncork();
return !self._client.should_buffer;
}
var callback_without_own_cb = function (err, res) {
if (err) {
self.results.push(err);
// Add the position to the error
var i = self.results.length - 1;
self.results[i].position = i;
} else {
self.results.push(res);
}
// Do not emit an error here. Otherwise each error would result in one emit.
// The errors will be returned in the result anyway
};
var last_callback = function (cb) {
return function (err, res) {
cb(err, res);
callback(null, self.results);
};
};
self.results = [];
while (command_obj = self.queue.shift()) {
if (typeof command_obj.callback === 'function') {
command_obj.callback = batch_callback(self, command_obj.callback, index);
} else {
command_obj.callback = callback_without_own_cb;
}
if (typeof callback === 'function' && index === len - 1) {
command_obj.callback = last_callback(command_obj.callback);
}
this._client.internal_send_command(command_obj);
index++;
}
self._client.uncork();
return !self._client.should_buffer;
};
module.exports = Multi;
node-redis-3.0.2/lib/utils.js 0000664 0000000 0000000 00000010327 13620024551 0016012 0 ustar 00root root 0000000 0000000 'use strict';
// hgetall converts its replies to an Object. If the reply is empty, null is returned.
// These function are only called with internal data and have therefore always the same instanceof X
function replyToObject (reply) {
// The reply might be a string or a buffer if this is called in a transaction (multi)
if (reply.length === 0 || !(reply instanceof Array)) {
return null;
}
var obj = {};
for (var i = 0; i < reply.length; i += 2) {
obj[reply[i].toString('binary')] = reply[i + 1];
}
return obj;
}
function replyToStrings (reply) {
if (reply instanceof Buffer) {
return reply.toString();
}
if (reply instanceof Array) {
var res = new Array(reply.length);
for (var i = 0; i < reply.length; i++) {
// Recusivly call the function as slowlog returns deep nested replies
res[i] = replyToStrings(reply[i]);
}
return res;
}
return reply;
}
function print (err, reply) {
if (err) {
// A error always begins with Error:
console.log(err.toString());
} else {
console.log('Reply: ' + reply);
}
}
var camelCase;
// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
// All capital letters are going to be replaced with a lower case letter and a underscore infront of it
function clone (obj) {
var copy;
if (Array.isArray(obj)) {
copy = new Array(obj.length);
for (var i = 0; i < obj.length; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
if (Object.prototype.toString.call(obj) === '[object Object]') {
copy = {};
var elems = Object.keys(obj);
var elem;
while (elem = elems.pop()) {
if (elem === 'tls') { // special handle tls
copy[elem] = obj[elem];
continue;
}
// Accept camelCase options and convert them to snake_case
var snake_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase();
// If camelCase is detected, pass it to the client, so all variables are going to be camelCased
// There are no deep nested options objects yet, but let's handle this future proof
if (snake_case !== elem.toLowerCase()) {
camelCase = true;
}
copy[snake_case] = clone(obj[elem]);
}
return copy;
}
return obj;
}
function convenienceClone (obj) {
camelCase = false;
obj = clone(obj) || {};
if (camelCase) {
obj.camel_case = true;
}
return obj;
}
function callbackOrEmit (self, callback, err, res) {
if (callback) {
callback(err, res);
} else if (err) {
self.emit('error', err);
}
}
function replyInOrder (self, callback, err, res, queue) {
// If the queue is explicitly passed, use that, otherwise fall back to the offline queue first,
// as there might be commands in both queues at the same time
var command_obj;
/* istanbul ignore if: TODO: Remove this as soon as we test Redis 3.2 on travis */
if (queue) {
command_obj = queue.peekBack();
} else {
command_obj = self.offline_queue.peekBack() || self.command_queue.peekBack();
}
if (!command_obj) {
process.nextTick(function () {
callbackOrEmit(self, callback, err, res);
});
} else {
var tmp = command_obj.callback;
command_obj.callback = tmp ?
function (e, r) {
tmp(e, r);
callbackOrEmit(self, callback, err, res);
} :
function (e, r) {
if (e) {
self.emit('error', e);
}
callbackOrEmit(self, callback, err, res);
};
}
}
module.exports = {
reply_to_strings: replyToStrings,
reply_to_object: replyToObject,
print: print,
err_code: /^([A-Z]+)\s+(.+)$/,
monitor_regex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]+ .+\]( ".+?")+$/,
clone: convenienceClone,
callback_or_emit: callbackOrEmit,
reply_in_order: replyInOrder
};
node-redis-3.0.2/package.json 0000664 0000000 0000000 00000003522 13620024551 0016033 0 ustar 00root root 0000000 0000000 {
"name": "redis",
"version": "3.0.2",
"description": "A high performance Redis client.",
"keywords": [
"database",
"redis",
"transaction",
"pipelining",
"performance",
"queue",
"nodejs",
"pubsub",
"backpressure"
],
"author": "Matt Ranney ",
"contributors": [
{
"name": "Mike Diarmid (Salakar)",
"url": "https://github.com/salakar"
},
{
"name": "Ruben Bridgewater (BridgeAR)",
"url": "https://github.com/BridgeAR"
}
],
"license": "MIT",
"main": "./index.js",
"scripts": {
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"coverage": "nyc report --reporter=html",
"benchmark": "node benchmarks/multi_bench.js",
"test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000",
"lint": "eslint . --fix && npm run coverage",
"compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt"
},
"dependencies": {
"denque": "^1.4.1",
"redis-commands": "^1.5.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
},
"engines": {
"node": ">=6"
},
"devDependencies": {
"prettier": "^1.19.1",
"bluebird": "^3.7.2",
"coveralls": "^2.11.2",
"eslint": "^6.8.0",
"intercept-stdout": "~0.1.2",
"metrics": "^0.1.21",
"mocha": "^4.1.0",
"nyc": "^14.1.1",
"tcp-port-used": "^1.0.1",
"uuid": "^3.4.0",
"cross-spawn": "^6.0.5"
},
"repository": {
"type": "git",
"url": "git://github.com/NodeRedis/node-redis.git"
},
"bugs": {
"url": "https://github.com/NodeRedis/node-redis/issues"
},
"homepage": "https://github.com/NodeRedis/node-redis",
"directories": {
"example": "examples",
"test": "test"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-redis"
}
}
node-redis-3.0.2/test/ 0000775 0000000 0000000 00000000000 13620024551 0014522 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/test/auth.spec.js 0000664 0000000 0000000 00000036316 13620024551 0016763 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
if (process.platform === 'win32') {
// TODO: Fix redis process spawn on windows
return;
}
describe('client authentication', function () {
before(function (done) {
helper.stopRedis(function () {
helper.startRedis('./conf/password.conf', done);
});
});
helper.allTests({
allConnections: true
}, function (ip, args) {
describe('using ' + ip, function () {
var auth = 'porkchopsandwiches';
var client = null;
beforeEach(function () {
client = null;
});
afterEach(function () {
// Explicitly ignore still running commands
// The ready command could still be running
client.end(false);
});
it("allows auth to be provided with 'auth' method", function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.auth(auth, function (err, res) {
assert.strictEqual(null, err);
assert.strictEqual('OK', res.toString());
return done(err);
});
});
it('support redis 2.4 with retrying auth commands if still loading', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
var time = Date.now();
client.auth(auth, function (err, res) {
assert.strictEqual('retry worked', res);
var now = Date.now();
// Hint: setTimeout sometimes triggers early and therefore the value can be like one or two ms to early
assert(now - time >= 98, 'Time should be above 100 ms (the reconnect time) and is ' + (now - time));
assert(now - time < 225, 'Time should be below 255 ms (the reconnect should only take a bit above 100 ms) and is ' + (now - time));
done();
});
var tmp = client.command_queue.get(0).callback;
client.command_queue.get(0).callback = function (err, res) {
client.auth = function (pass, callback) {
callback(null, 'retry worked');
};
tmp(new Error('ERR redis is still LOADING'));
};
});
it('emits error when auth is bad without callback', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.once('error', function (err) {
assert.strictEqual(err.command, 'AUTH');
assert.ok(/ERR invalid password/.test(err.message));
return done();
});
client.auth(auth + 'bad');
});
it('returns an error when auth is bad (empty string) with a callback', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.auth('', function (err, res) {
assert.strictEqual(err.command, 'AUTH');
assert.ok(/ERR invalid password/.test(err.message));
done();
});
});
if (ip === 'IPv4') {
it('allows auth to be provided as part of redis url and do not fire commands before auth is done', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var end = helper.callFuncAfter(done, 2);
client = redis.createClient('redis://:' + auth + '@' + config.HOST[ip] + ':' + config.PORT);
client.on('ready', function () {
end();
});
// The info command may be used while loading but not if not yet authenticated
client.info(function (err, res) {
assert(!err);
end();
});
});
it('allows auth and database to be provided as part of redis url query parameter', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT + '?db=2&password=' + auth);
assert.strictEqual(client.options.db, '2');
assert.strictEqual(client.options.password, auth);
assert.strictEqual(client.auth_pass, auth);
client.on('ready', function () {
// Set a key so the used database is returned in the info command
client.set('foo', 'bar');
client.get('foo');
assert.strictEqual(client.server_info.db2, undefined);
// Using the info command should update the server_info
client.info(function (err, res) {
assert(typeof client.server_info.db2 === 'object');
});
client.flushdb(done);
});
});
}
it('allows auth to be provided as config option for client', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(ip, {
auth_pass: auth
});
client = redis.createClient.apply(null, args);
client.on('ready', done);
});
it('allows auth and no_ready_check to be provided as config option for client', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(ip, {
password: auth,
no_ready_check: true
});
client = redis.createClient.apply(null, args);
client.on('ready', done);
});
it('allows auth to be provided post-hoc with auth method', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(ip);
client = redis.createClient.apply(null, args);
client.auth(auth);
client.on('ready', done);
});
it('reconnects with appropriate authentication while offline commands are present', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.auth(auth);
client.on('ready', function () {
if (this.times_connected < 3) {
var interval = setInterval(function () {
if (client.commandQueueLength !== 0) {
return;
}
clearInterval(interval);
interval = null;
client.stream.destroy();
client.set('foo', 'bar');
client.get('foo'); // Errors would bubble
assert.strictEqual(client.offlineQueueLength, 2);
}, 1);
} else {
done();
}
});
client.on('reconnecting', function (params) {
assert.strictEqual(params.error, null);
});
});
it('should return an error if the password is not correct and a callback has been provided', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
var async = true;
client.auth('undefined', function (err, res) {
assert.strictEqual(err.message, 'ERR invalid password');
assert.strictEqual(err.command, 'AUTH');
assert.strictEqual(res, undefined);
async = false;
done();
});
assert(async);
});
it('should emit an error if the password is not correct and no callback has been provided', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.on('error', function (err) {
assert.strictEqual(err.message, 'ERR invalid password');
assert.strictEqual(err.command, 'AUTH');
done();
});
client.auth(234567);
});
it('allows auth to be provided post-hoc with auth method again', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(ip, {
auth_pass: auth
});
client = redis.createClient.apply(null, args);
client.on('ready', function () {
client.auth(auth, helper.isString('OK', done));
});
});
it('does not allow any commands to be processed if not authenticated using no_ready_check true', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(ip, {
no_ready_check: true
});
client = redis.createClient.apply(null, args);
client.on('ready', function () {
client.set('foo', 'bar', function (err, res) {
assert.equal(err.message, 'NOAUTH Authentication required.');
assert.equal(err.code, 'NOAUTH');
assert.equal(err.command, 'SET');
done();
});
});
});
it('does not allow auth to be provided post-hoc with auth method if not authenticated before', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.on('error', function (err) {
assert.equal(err.code, 'NOAUTH');
assert.equal(err.message, 'Ready check failed: NOAUTH Authentication required.');
assert.equal(err.command, 'INFO');
done();
});
});
it('should emit an error if the provided password is faulty', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient({
password: 'wrong_password',
});
client.once('error', function (err) {
assert.strictEqual(err.message, 'ERR invalid password');
done();
});
});
it('pubsub working with auth', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(ip, {
password: auth
});
client = redis.createClient.apply(null, args);
client.set('foo', 'bar');
client.subscribe('somechannel', 'another channel', function (err, res) {
client.once('ready', function () {
assert.strictEqual(client.pub_sub_mode, 1);
client.get('foo', function (err, res) {
assert(/ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message));
done();
});
});
});
client.once('ready', function () {
// Coherent behavior with all other offline commands fires commands before emitting but does not wait till they return
assert.strictEqual(client.pub_sub_mode, 2);
client.ping(function () { // Make sure all commands were properly processed already
client.stream.destroy();
});
});
});
it('individual commands work properly with batch', function (done) {
// quit => might return an error instead of "OK" in the exec callback... (if not connected)
// auth => might return an error instead of "OK" in the exec callback... (if no password is required / still loading on Redis <= 2.4)
// This could be fixed by checking the return value of the callback in the exec callback and
// returning the manipulated [error, result] from the callback.
// There should be a better solution though
var args = config.configureClient('localhost', {
noReadyCheck: true
});
client = redis.createClient.apply(null, args);
assert.strictEqual(client.selected_db, undefined);
var end = helper.callFuncAfter(done, 8);
client.on('monitor', function () {
end(); // Should be called for each command after monitor
});
client.batch()
.auth(auth)
.SELECT(5, function (err, res) {
assert.strictEqual(client.selected_db, 5);
assert.strictEqual(res, 'OK');
assert.notDeepEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 });
})
.monitor()
.set('foo', 'bar', helper.isString('OK'))
.INFO('stats', function (err, res) {
assert.strictEqual(res.indexOf('# Stats\r\n'), 0);
assert.strictEqual(client.serverInfo.sync_full, '0');
})
.get('foo', helper.isString('bar'))
.subscribe(['foo', 'bar'])
.unsubscribe('foo')
.SUBSCRIBE('/foo', helper.isString('/foo'))
.psubscribe('*')
.quit(helper.isString('OK')) // this might be interesting
.exec(function (err, res) {
res[4] = res[4].substr(0, 9);
assert.deepEqual(res, ['OK', 'OK', 'OK', 'OK', '# Stats\r\n', 'bar', 'bar', 'foo', '/foo', '*', 'OK']);
end();
});
});
});
});
after(function (done) {
if (helper.redisProcess().spawnFailed()) return done();
helper.stopRedis(function () {
helper.startRedis('./conf/redis.conf', done);
});
});
});
node-redis-3.0.2/test/batch.spec.js 0000664 0000000 0000000 00000040273 13620024551 0017100 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
describe("The 'batch' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('connect', function () {
client.quit();
});
client.on('end', done);
});
it('returns an empty array for missing commands', function (done) {
var batch = client.batch();
batch.exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.length, 0);
done();
});
});
it('returns an error for batch with commands', function (done) {
var batch = client.batch();
batch.set('foo', 'bar');
batch.exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res[0].code, 'NR_CLOSED');
done();
});
});
it('returns an empty array for missing commands if promisified', function () {
return client.batch().execAsync().then(function (res) {
assert.strictEqual(res.length, 0);
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(function (err) {
return done(err);
});
});
});
afterEach(function () {
client.end(true);
});
it('returns an empty array and keep the execution order in takt', function (done) {
var called = false;
client.set('foo', 'bar', function (err, res) {
called = true;
});
var batch = client.batch();
batch.exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.length, 0);
assert(called);
done();
});
});
it('runs normal calls inbetween batch', function (done) {
var batch = client.batch();
batch.set('m1', '123');
client.set('m2', '456', done);
});
it('returns an empty array if promisified', function () {
return client.batch().execAsync().then(function (res) {
assert.strictEqual(res.length, 0);
});
});
it('returns an empty result array', function (done) {
var batch = client.batch();
var async = true;
var notBuffering = batch.exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.length, 0);
async = false;
done();
});
assert(async);
assert.strictEqual(notBuffering, true);
});
it('fail individually when one command fails using chaining notation', function (done) {
var batch1, batch2;
batch1 = client.batch();
batch1.mset('batchfoo', '10', 'batchbar', '20', helper.isString('OK'));
// Provoke an error at queue time
batch1.set('foo2', helper.isError());
batch1.incr('batchfoo');
batch1.incr('batchbar');
batch1.exec(function () {
// Confirm that the previous command, while containing an error, still worked.
batch2 = client.batch();
batch2.get('foo2', helper.isNull());
batch2.incr('batchbar', helper.isNumber(22));
batch2.incr('batchfoo', helper.isNumber(12));
batch2.exec(function (err, replies) {
assert.strictEqual(null, replies[0]);
assert.strictEqual(22, replies[1]);
assert.strictEqual(12, replies[2]);
return done();
});
});
});
it('fail individually when one command fails and emit the error if no callback has been provided', function (done) {
var batch1;
client.on('error', function (err) {
done(err);
});
batch1 = client.batch();
batch1.mset('batchfoo', '10', 'batchbar', '20', helper.isString('OK'));
// Provoke an error at queue time
batch1.set('foo2');
batch1.incr('batchfoo');
batch1.incr('batchbar');
batch1.exec(function (err, res) {
assert.strictEqual(res[1].command, 'SET');
assert.strictEqual(res[1].code, 'ERR');
done();
});
});
it('fail individually when one command in an array of commands fails', function (done) {
// test nested batch-bulk replies
client.batch([
['mget', 'batchfoo', 'batchbar', function (err, res) {
assert.strictEqual(2, res.length);
assert.strictEqual(0, +res[0]);
assert.strictEqual(0, +res[1]);
}],
['set', 'foo2', helper.isError()],
['incr', 'batchfoo'],
['incr', 'batchbar']
]).exec(function (err, replies) {
assert.strictEqual(2, replies[0].length);
assert.strictEqual(null, replies[0][0]);
assert.strictEqual(null, replies[0][1]);
assert.strictEqual('SET', replies[1].command);
assert.strictEqual('1', replies[2].toString());
assert.strictEqual('1', replies[3].toString());
return done();
});
});
it('handles multiple operations being applied to a set', function (done) {
client.sadd('some set', 'mem 1');
client.sadd(['some set', 'mem 2']);
client.sadd('some set', 'mem 3');
client.sadd('some set', 'mem 4');
// make sure empty mb reply works
client.del('some missing set');
client.smembers('some missing set', function (err, reply) {
// make sure empty mb reply works
assert.strictEqual(0, reply.length);
});
// test nested batch-bulk replies with empty mb elements.
client.BATCH([
['smembers', ['some set']],
['del', 'some set'],
['smembers', 'some set', undefined] // The explicit undefined is handled as a callback that is undefined
])
.scard('some set')
.exec(function (err, replies) {
assert.strictEqual(4, replies[0].length);
assert.strictEqual(0, replies[2].length);
return done();
});
});
it('allows multiple operations to be performed using constructor with all kinds of syntax', function (done) {
var now = Date.now();
var arr = ['batchhmset', 'batchbar', 'batchbaz'];
var arr2 = ['some manner of key', 'otherTypes'];
var arr3 = [5768, 'batchbarx', 'batchfoox'];
var arr4 = ['mset', [578, 'batchbar'], helper.isString('OK')];
client.batch([
arr4,
[['mset', 'batchfoo2', 'batchbar2', 'batchfoo3', 'batchbar3'], helper.isString('OK')],
['hmset', arr],
[['hmset', 'batchhmset2', 'batchbar2', 'batchfoo3', 'batchbar3', 'test'], helper.isString('OK')],
['hmset', ['batchhmset', 'batchbar', 'batchfoo'], helper.isString('OK')],
['hmset', arr3, helper.isString('OK')],
['hmset', now, {123456789: 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}],
['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}, helper.isString('OK')],
['HMSET', 'batchhmset', ['batchbar', 'batchbaz']],
['hmset', 'batchhmset', ['batchbar', 'batchbaz'], helper.isString('OK')],
])
.hmget(now, 123456789, 'otherTypes')
.hmget('key2', arr2, function noop () {})
.hmget(['batchhmset2', 'some manner of key', 'batchbar3'])
.mget('batchfoo2', ['batchfoo3', 'batchfoo'], function (err, res) {
assert.strictEqual(res[0], 'batchbar2');
assert.strictEqual(res[1], 'batchbar3');
assert.strictEqual(res[2], null);
})
.exec(function (err, replies) {
assert.equal(arr.length, 3);
assert.equal(arr2.length, 2);
assert.equal(arr3.length, 3);
assert.equal(arr4.length, 3);
assert.strictEqual(null, err);
assert.equal(replies[10][1], '555');
assert.equal(replies[11][0], 'a type of value');
assert.strictEqual(replies[12][0], null);
assert.equal(replies[12][1], 'test');
assert.equal(replies[13][0], 'batchbar2');
assert.equal(replies[13].length, 3);
assert.equal(replies.length, 14);
return done();
});
});
it('converts a non string key to a string', function (done) {
// TODO: Converting the key might change soon again.
client.batch().hmset(true, {
test: 123,
bar: 'baz'
}).exec(done);
});
it('runs a batch without any further commands', function (done) {
var buffering = client.batch().exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.length, 0);
done();
});
assert(typeof buffering === 'boolean');
});
it('runs a batch without any further commands and without callback', function () {
var buffering = client.batch().exec();
assert.strictEqual(buffering, true);
});
it('allows multiple operations to be performed using a chaining API', function (done) {
client.batch()
.mset('some', '10', 'keys', '20')
.incr('some')
.incr('keys')
.mget('some', 'keys')
.exec(function (err, replies) {
assert.strictEqual(null, err);
assert.equal('OK', replies[0]);
assert.equal(11, replies[1]);
assert.equal(21, replies[2]);
assert.equal(11, replies[3][0].toString());
assert.equal(21, replies[3][1].toString());
return done();
});
});
it('allows multiple commands to work the same as normal to be performed using a chaining API', function (done) {
client.batch()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.exec(function (err, replies) {
assert.strictEqual(null, err);
assert.equal('OK', replies[0]);
assert.equal(11, replies[1]);
assert.equal(21, replies[2]);
assert.equal(11, replies[3][0].toString());
assert.equal(21, replies[3][1].toString());
return done();
});
});
it('allows multiple commands to work the same as normal to be performed using a chaining API promisified', function () {
return client.batch()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.execAsync()
.then(function (replies) {
assert.equal('OK', replies[0]);
assert.equal(11, replies[1]);
assert.equal(21, replies[2]);
assert.equal(11, replies[3][0].toString());
assert.equal(21, replies[3][1].toString());
});
});
it('allows an array to be provided indicating multiple operations to perform', function (done) {
// test nested batch-bulk replies with nulls.
client.batch([
['mget', ['batchfoo', 'some', 'random value', 'keys']],
['incr', 'batchfoo']
])
.exec(function (err, replies) {
assert.strictEqual(replies.length, 2);
assert.strictEqual(replies[0].length, 4);
return done();
});
});
it('allows multiple operations to be performed on a hash', function (done) {
client.batch()
.hmset('batchhash', 'a', 'foo', 'b', 1)
.hmset('batchhash', {
extra: 'fancy',
things: 'here'
})
.hgetall('batchhash')
.exec(done);
});
it('should work without any callback or arguments', function (done) {
var batch = client.batch();
batch.set('baz', 'binary');
batch.set('foo', 'bar');
batch.ping();
batch.exec();
client.get('foo', helper.isString('bar', done));
});
});
});
});
});
node-redis-3.0.2/test/commands/ 0000775 0000000 0000000 00000000000 13620024551 0016323 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/test/commands/blpop.spec.js 0000664 0000000 0000000 00000006042 13620024551 0020730 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var intercept = require('intercept-stdout');
describe("The 'blpop' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
var bclient;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('pops value immediately if list contains values', function (done) {
bclient = redis.createClient.apply(null, args);
redis.debug_mode = true;
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
return '';
});
client.rpush('blocking list', 'initial value', helper.isNumber(1));
unhookIntercept();
assert(/Send 127\.0\.0\.1:6379 id [0-9]+: \*3\r\n\$5\r\nrpush\r\n\$13\r\nblocking list\r\n\$13\r\ninitial value\r\n\n$/.test(text));
redis.debug_mode = false;
bclient.blpop('blocking list', 0, function (err, value) {
assert.strictEqual(value[0], 'blocking list');
assert.strictEqual(value[1], 'initial value');
return done(err);
});
});
it('pops value immediately if list contains values using array notation', function (done) {
bclient = redis.createClient.apply(null, args);
client.rpush(['blocking list', 'initial value'], helper.isNumber(1));
bclient.blpop(['blocking list', 0], function (err, value) {
assert.strictEqual(value[0], 'blocking list');
assert.strictEqual(value[1], 'initial value');
return done(err);
});
});
it('waits for value if list is not yet populated', function (done) {
bclient = redis.createClient.apply(null, args);
bclient.blpop('blocking list 2', 5, function (err, value) {
assert.strictEqual(value[0], 'blocking list 2');
assert.strictEqual(value[1], 'initial value');
return done(err);
});
client.rpush('blocking list 2', 'initial value', helper.isNumber(1));
});
it('times out after specified time', function (done) {
bclient = redis.createClient.apply(null, args);
bclient.BLPOP('blocking list', 1, function (err, res) {
assert.strictEqual(res, null);
return done(err);
});
});
afterEach(function () {
client.end(true);
bclient.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/client.spec.js 0000664 0000000 0000000 00000015206 13620024551 0021074 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'client' method", function () {
helper.allTests(function (ip, args) {
var pattern = /addr=/;
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
client.end(true);
});
describe('list', function () {
it('lists connected clients', function (done) {
client.client('LIST', helper.match(pattern, done));
});
it("lists connected clients when invoked with multi's chaining syntax", function (done) {
client.multi().client('list', helper.isType.string()).exec(helper.match(pattern, done));
});
it('lists connected clients when invoked with array syntax on client', function (done) {
client.multi().client(['list']).exec(helper.match(pattern, done));
});
it("lists connected clients when invoked with multi's array syntax", function (done) {
client.multi([
['client', 'list']
]).exec(helper.match(pattern, done));
});
});
describe('reply', function () {
describe('as normal command', function () {
it('on', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
assert.strictEqual(client.reply, 'ON');
client.client('reply', 'on', helper.isString('OK'));
assert.strictEqual(client.reply, 'ON');
client.set('foo', 'bar', done);
});
it('off', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
assert.strictEqual(client.reply, 'ON');
client.client(new Buffer('REPLY'), 'OFF', helper.isUndefined());
assert.strictEqual(client.reply, 'OFF');
client.set('foo', 'bar', helper.isUndefined(done));
});
it('skip', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
assert.strictEqual(client.reply, 'ON');
client.client('REPLY', new Buffer('SKIP'), helper.isUndefined());
assert.strictEqual(client.reply, 'SKIP_ONE_MORE');
client.set('foo', 'bar', helper.isUndefined());
client.get('foo', helper.isString('bar', done));
});
});
describe('in a batch context', function () {
it('on', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
var batch = client.batch();
assert.strictEqual(client.reply, 'ON');
batch.client('reply', 'on', helper.isString('OK'));
assert.strictEqual(client.reply, 'ON');
batch.set('foo', 'bar');
batch.exec(function (err, res) {
assert.deepEqual(res, ['OK', 'OK']);
done(err);
});
});
it('off', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
var batch = client.batch();
assert.strictEqual(client.reply, 'ON');
batch.set('hello', 'world');
batch.client(new Buffer('REPLY'), new Buffer('OFF'), helper.isUndefined());
batch.set('foo', 'bar', helper.isUndefined());
batch.exec(function (err, res) {
assert.strictEqual(client.reply, 'OFF');
assert.deepEqual(res, ['OK', undefined, undefined]);
done(err);
});
});
it('skip', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
assert.strictEqual(client.reply, 'ON');
client.batch()
.set('hello', 'world')
.client('REPLY', 'SKIP', helper.isUndefined())
.set('foo', 'bar', helper.isUndefined())
.get('foo')
.exec(function (err, res) {
assert.strictEqual(client.reply, 'ON');
assert.deepEqual(res, ['OK', undefined, undefined, 'bar']);
done(err);
});
});
});
});
describe('setname / getname', function () {
var client2;
beforeEach(function (done) {
client2 = redis.createClient.apply(null, args);
client2.once('ready', function () {
done();
});
});
afterEach(function () {
client2.end(true);
});
it('sets the name', function (done) {
// The querys are auto pipelined and the response is a response to all querys of one client
// per chunk. So the execution order is only garanteed on each client
var end = helper.callFuncAfter(done, 2);
client.client('setname', 'RUTH');
client2.client('setname', ['RENEE'], helper.isString('OK'));
client2.client(['setname', 'MARTIN'], helper.isString('OK'));
client2.client('getname', function (err, res) {
assert.equal(res, 'MARTIN');
end();
});
client.client('getname', function (err, res) {
assert.equal(res, 'RUTH');
end();
});
});
});
});
});
});
node-redis-3.0.2/test/commands/dbsize.spec.js 0000664 0000000 0000000 00000006356 13620024551 0021104 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var uuid = require('uuid');
describe("The 'dbsize' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var key, value;
beforeEach(function () {
key = uuid.v4();
value = uuid.v4();
});
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('reports an error', function (done) {
client.dbsize([], function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(function (err, res) {
helper.isString('OK')(err, res);
done();
});
});
});
afterEach(function () {
client.end(true);
});
it('returns a zero db size', function (done) {
client.DBSIZE([], function (err, res) {
helper.isNotError()(err, res);
helper.isType.number()(err, res);
assert.strictEqual(res, 0, 'Initial db size should be 0');
done();
});
});
describe('when more data is added to Redis', function () {
var oldSize;
beforeEach(function (done) {
client.dbsize(function (err, res) {
helper.isType.number()(err, res);
assert.strictEqual(res, 0, 'Initial db size should be 0');
oldSize = res;
client.set(key, value, function (err, res) {
helper.isNotError()(err, res);
done();
});
});
});
it('returns a larger db size', function (done) {
client.dbsize([], function (err, res) {
helper.isNotError()(err, res);
helper.isType.positiveNumber()(err, res);
assert.strictEqual(true, (oldSize < res), 'Adding data should increase db size.');
done();
});
});
});
});
});
});
});
node-redis-3.0.2/test/commands/del.spec.js 0000664 0000000 0000000 00000003771 13620024551 0020366 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'del' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('allows a single key to be deleted', function (done) {
client.set('foo', 'bar');
client.DEL('foo', helper.isNumber(1));
client.get('foo', helper.isNull(done));
});
it('allows del to be called on a key that does not exist', function (done) {
client.del('foo', helper.isNumber(0, done));
});
it('allows multiple keys to be deleted', function (done) {
client.mset('foo', 'bar', 'apple', 'banana');
client.del('foo', 'apple', helper.isNumber(2));
client.get('foo', helper.isNull());
client.get('apple', helper.isNull(done));
});
it('allows multiple keys to be deleted with the array syntax', function (done) {
client.mset('foo', 'bar', 'apple', 'banana');
client.del(['foo', 'apple'], helper.isNumber(2));
client.get('foo', helper.isNull());
client.get('apple', helper.isNull(done));
});
it('allows multiple keys to be deleted with the array syntax and no callback', function (done) {
client.mset('foo', 'bar', 'apple', 'banana');
client.del(['foo', 'apple']);
client.get('foo', helper.isNull());
client.get('apple', helper.isNull(done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/eval.spec.js 0000664 0000000 0000000 00000023120 13620024551 0020537 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var crypto = require('crypto');
var helper = require('../helper');
var redis = config.redis;
describe("The 'eval' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
var source = "return redis.call('set', 'sha', 'test')";
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
client.end(true);
});
it('converts a float to an integer when evaluated', function (done) {
client.eval('return 100.5', 0, helper.isNumber(100, done));
});
it('returns a string', function (done) {
client.eval("return 'hello world'", 0, helper.isString('hello world', done));
});
it('converts boolean true to integer 1', function (done) {
client.eval('return true', 0, helper.isNumber(1, done));
});
it('converts boolean false to null', function (done) {
client.eval('return false', 0, helper.isNull(done));
});
it('converts lua status code to string representation', function (done) {
client.eval("return {ok='fine'}", 0, helper.isString('fine', done));
});
it('converts lua error to an error response', function (done) {
client.eval("return {err='this is an error'}", 0, function (err) {
assert(err.code === undefined);
helper.isError()(err);
done();
});
});
it('represents a lua table appropritely', function (done) {
client.eval("return {1,2,3,'ciao',{1,2}}", 0, function (err, res) {
assert.strictEqual(5, res.length);
assert.strictEqual(1, res[0]);
assert.strictEqual(2, res[1]);
assert.strictEqual(3, res[2]);
assert.strictEqual('ciao', res[3]);
assert.strictEqual(2, res[4].length);
assert.strictEqual(1, res[4][0]);
assert.strictEqual(2, res[4][1]);
return done();
});
});
it('populates keys and argv correctly', function (done) {
client.eval('return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'a', 'b', 'c', 'd', function (err, res) {
assert.strictEqual(4, res.length);
assert.strictEqual('a', res[0]);
assert.strictEqual('b', res[1]);
assert.strictEqual('c', res[2]);
assert.strictEqual('d', res[3]);
return done();
});
});
it('allows arguments to be provided in array rather than as multiple parameters', function (done) {
client.eval(['return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'a', 'b', 'c', 'd'], function (err, res) {
assert.strictEqual(4, res.length);
assert.strictEqual('a', res[0]);
assert.strictEqual('b', res[1]);
assert.strictEqual('c', res[2]);
assert.strictEqual('d', res[3]);
return done();
});
});
it('allows a script to be executed that accesses the redis API without callback', function (done) {
client.eval(source, 0);
client.get('sha', helper.isString('test', done));
});
describe('evalsha', function () {
var sha = crypto.createHash('sha1').update(source).digest('hex');
it('allows a script to be executed that accesses the redis API', function (done) {
client.eval(source, 0, helper.isString('OK'));
client.get('sha', helper.isString('test', done));
});
it('can execute a script if the SHA exists', function (done) {
client.evalsha(sha, 0, helper.isString('OK'));
client.get('sha', helper.isString('test', done));
});
it('returns an error if SHA does not exist', function (done) {
client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0, helper.isError(done));
});
it('emit an error if SHA does not exist without any callback', function (done) {
client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0);
client.on('error', function (err) {
assert.equal(err.code, 'NOSCRIPT');
assert(/NOSCRIPT No matching script. Please use EVAL./.test(err.message));
done();
});
});
it('emits an error if SHA does not exist and no callback has been provided', function (done) {
client.on('error', function (err) {
assert.equal(err.message, 'NOSCRIPT No matching script. Please use EVAL.');
done();
});
client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0);
});
});
it('allows a key to be incremented, and performs appropriate conversion from LUA type', function (done) {
client.set('incr key', 0, function (err, reply) {
if (err) return done(err);
client.eval("local foo = redis.call('incr','incr key')\nreturn {type(foo),foo}", 0, function (err, res) {
assert.strictEqual(2, res.length);
assert.strictEqual('number', res[0]);
assert.strictEqual(1, res[1]);
return done(err);
});
});
});
it('allows a bulk operation to be performed, and performs appropriate conversion from LUA type', function (done) {
client.set('bulk reply key', 'bulk reply value', function (err, res) {
client.eval("local foo = redis.call('get','bulk reply key'); return {type(foo),foo}", 0, function (err, res) {
assert.strictEqual(2, res.length);
assert.strictEqual('string', res[0]);
assert.strictEqual('bulk reply value', res[1]);
return done(err);
});
});
});
it('allows a multi mulk operation to be performed, with the appropriate type conversion', function (done) {
client.multi()
.del('mylist')
.rpush('mylist', 'a')
.rpush('mylist', 'b')
.rpush('mylist', 'c')
.exec(function (err, replies) {
if (err) return done(err);
client.eval("local foo = redis.call('lrange','mylist',0,-1); return {type(foo),foo[1],foo[2],foo[3],# foo}", 0, function (err, res) {
assert.strictEqual(5, res.length);
assert.strictEqual('table', res[0]);
assert.strictEqual('a', res[1]);
assert.strictEqual('b', res[2]);
assert.strictEqual('c', res[3]);
assert.strictEqual(3, res[4]);
return done(err);
});
});
});
it('returns an appropriate representation of Lua status reply', function (done) {
client.eval("local foo = redis.call('set','mykey','myval'); return {type(foo),foo['ok']}", 0, function (err, res) {
assert.strictEqual(2, res.length);
assert.strictEqual('table', res[0]);
assert.strictEqual('OK', res[1]);
return done(err);
});
});
it('returns an appropriate representation of a Lua error reply', function (done) {
client.set('error reply key', 'error reply value', function (err, res) {
if (err) return done(err);
client.eval("local foo = redis.pcall('incr','error reply key'); return {type(foo),foo['err']}", 0, function (err, res) {
assert.strictEqual(2, res.length);
assert.strictEqual('table', res[0]);
assert.strictEqual('ERR value is not an integer or out of range', res[1]);
return done(err);
});
});
});
it('returns an appropriate representation of a Lua nil reply', function (done) {
client.del('nil reply key', function (err, res) {
if (err) return done(err);
client.eval("local foo = redis.call('get','nil reply key'); return {type(foo),foo == false}", 0, function (err, res) {
if (err) throw err;
assert.strictEqual(2, res.length);
assert.strictEqual('boolean', res[0]);
assert.strictEqual(1, res[1]);
return done(err);
});
});
});
});
});
});
node-redis-3.0.2/test/commands/exists.spec.js 0000664 0000000 0000000 00000002220 13620024551 0021125 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'exists' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns 1 if the key exists', function (done) {
client.set('foo', 'bar');
client.EXISTS('foo', helper.isNumber(1, done));
});
it('returns 1 if the key exists with array syntax', function (done) {
client.set('foo', 'bar');
client.EXISTS(['foo'], helper.isNumber(1, done));
});
it('returns 0 if the key does not exist', function (done) {
client.exists('bar', helper.isNumber(0, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/expire.spec.js 0000664 0000000 0000000 00000002537 13620024551 0021115 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'expire' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('expires key after timeout', function (done) {
client.set(['expiry key', 'bar'], helper.isString('OK'));
client.EXPIRE('expiry key', '1', helper.isNumber(1));
setTimeout(function () {
client.exists(['expiry key'], helper.isNumber(0, done));
}, 1050);
});
it('expires key after timeout with array syntax', function (done) {
client.set(['expiry key', 'bar'], helper.isString('OK'));
client.EXPIRE(['expiry key', '1'], helper.isNumber(1));
setTimeout(function () {
client.exists(['expiry key'], helper.isNumber(0, done));
}, 1050);
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/flushdb.spec.js 0000664 0000000 0000000 00000010174 13620024551 0021244 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var uuid = require('uuid');
describe("The 'flushdb' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var key, key2;
beforeEach(function () {
key = uuid.v4();
key2 = uuid.v4();
});
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('reports an error', function (done) {
client.flushdb(function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
done();
});
});
afterEach(function () {
client.end(true);
});
describe('when there is data in Redis', function () {
beforeEach(function (done) {
client.mset(key, uuid.v4(), key2, uuid.v4(), helper.isNotError());
client.dbsize([], function (err, res) {
helper.isType.positiveNumber()(err, res);
assert.equal(res, 2, 'Two keys should have been inserted');
done();
});
});
it('deletes all the keys', function (done) {
client.flushdb(function (err, res) {
assert.equal(res, 'OK');
client.mget(key, key2, function (err, res) {
assert.strictEqual(null, err, 'Unexpected error returned');
assert.strictEqual(true, Array.isArray(res), 'Results object should be an array.');
assert.strictEqual(2, res.length, 'Results array should have length 2.');
assert.strictEqual(null, res[0], 'Redis key should have been flushed.');
assert.strictEqual(null, res[1], 'Redis key should have been flushed.');
done(err);
});
});
});
it('results in a db size of zero', function (done) {
client.flushdb(function (err, res) {
client.dbsize([], function (err, res) {
helper.isNotError()(err, res);
helper.isType.number()(err, res);
assert.strictEqual(0, res, 'Flushing db should result in db size 0');
done();
});
});
});
it('results in a db size of zero without a callback', function (done) {
client.flushdb();
setTimeout(function (err, res) {
client.dbsize(function (err, res) {
helper.isNotError()(err, res);
helper.isType.number()(err, res);
assert.strictEqual(0, res, 'Flushing db should result in db size 0');
done();
});
}, 25);
});
});
});
});
});
});
node-redis-3.0.2/test/commands/geoadd.spec.js 0000664 0000000 0000000 00000002111 13620024551 0021030 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'geoadd' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns 1 if the key exists', function (done) {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]);
client.geoadd('mycity:21:0:location', '13.361389', '38.115556', 'COR', function (err, res) {
console.log(err, res);
// geoadd is still in the unstable branch. As soon as it reaches the stable one, activate this test
done();
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/get.spec.js 0000664 0000000 0000000 00000006247 13620024551 0020402 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var uuid = require('uuid');
describe("The 'get' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var key, value;
beforeEach(function () {
key = uuid.v4();
value = uuid.v4();
});
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('reports an error', function (done) {
client.get(key, function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
});
it('reports an error promisified', function () {
return client.getAsync(key).then(assert, function (err) {
assert(err.message.match(/The connection is already closed/));
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
done();
});
});
afterEach(function () {
client.end(true);
});
describe('when the key exists in Redis', function () {
beforeEach(function (done) {
client.set(key, value, function (err, res) {
helper.isNotError()(err, res);
done();
});
});
it('gets the value correctly', function (done) {
client.GET(key, function (err, res) {
helper.isString(value)(err, res);
done(err);
});
});
it("should not throw on a get without callback (even if it's not useful)", function (done) {
client.GET(key);
client.on('error', function (err) {
throw err;
});
setTimeout(done, 25);
});
});
describe('when the key does not exist in Redis', function () {
it('gets a null value', function (done) {
client.get(key, function (err, res) {
helper.isNull()(err, res);
done(err);
});
});
});
});
});
});
});
node-redis-3.0.2/test/commands/getset.spec.js 0000664 0000000 0000000 00000007363 13620024551 0021116 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var uuid = require('uuid');
describe("The 'getset' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var key, value, value2;
beforeEach(function () {
key = uuid.v4();
value = uuid.v4();
value2 = uuid.v4();
});
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('reports an error', function (done) {
client.get(key, function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
done();
});
});
afterEach(function () {
client.end(true);
});
describe('when the key exists in Redis', function () {
beforeEach(function (done) {
client.set(key, value, function (err, res) {
helper.isNotError()(err, res);
done();
});
});
it('gets the value correctly', function (done) {
client.GETSET(key, value2, function (err, res) {
helper.isString(value)(err, res);
client.get(key, function (err, res) {
helper.isString(value2)(err, res);
done(err);
});
});
});
it('gets the value correctly with array syntax', function (done) {
client.GETSET([key, value2], function (err, res) {
helper.isString(value)(err, res);
client.get(key, function (err, res) {
helper.isString(value2)(err, res);
done(err);
});
});
});
it('gets the value correctly with array syntax style 2', function (done) {
client.GETSET(key, [value2], function (err, res) {
helper.isString(value)(err, res);
client.get(key, function (err, res) {
helper.isString(value2)(err, res);
done(err);
});
});
});
});
describe('when the key does not exist in Redis', function () {
it('gets a null value', function (done) {
client.getset(key, value, function (err, res) {
helper.isNull()(err, res);
done(err);
});
});
});
});
});
});
});
node-redis-3.0.2/test/commands/hgetall.spec.js 0000664 0000000 0000000 00000007024 13620024551 0021235 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'hgetall' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
describe('regular client', function () {
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('handles simple keys and values', function (done) {
client.hmset(['hosts', 'hasOwnProperty', '1', 'another', '23', 'home', '1234'], helper.isString('OK'));
client.HGETALL(['hosts'], function (err, obj) {
assert.strictEqual(3, Object.keys(obj).length);
assert.strictEqual('1', obj.hasOwnProperty.toString());
assert.strictEqual('23', obj.another.toString());
assert.strictEqual('1234', obj.home.toString());
done(err);
});
});
it('handles fetching keys set using an object', function (done) {
client.batch().HMSET('msg_test', { message: 'hello' }, undefined).exec();
client.hgetall('msg_test', function (err, obj) {
assert.strictEqual(1, Object.keys(obj).length);
assert.strictEqual(obj.message, 'hello');
done(err);
});
});
it('handles fetching a messing key', function (done) {
client.hgetall('missing', function (err, obj) {
assert.strictEqual(null, obj);
done(err);
});
});
});
describe('binary client', function () {
var client;
var args = config.configureClient(ip, {
return_buffers: true
});
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns binary results', function (done) {
client.hmset(['bhosts', 'mjr', '1', 'another', '23', 'home', '1234', new Buffer([0xAA, 0xBB, 0x00, 0xF0]), new Buffer([0xCC, 0xDD, 0x00, 0xF0])], helper.isString('OK'));
client.HGETALL('bhosts', function (err, obj) {
assert.strictEqual(4, Object.keys(obj).length);
assert.strictEqual('1', obj.mjr.toString());
assert.strictEqual('23', obj.another.toString());
assert.strictEqual('1234', obj.home.toString());
assert.strictEqual((new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary'), Object.keys(obj)[3]);
assert.strictEqual((new Buffer([0xCC, 0xDD, 0x00, 0xF0])).toString('binary'), obj[(new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary')].toString('binary'));
return done(err);
});
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/hincrby.spec.js 0000664 0000000 0000000 00000002144 13620024551 0021251 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'hincrby' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
var hash = 'test hash';
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('increments a key that has already been set', function (done) {
var field = 'field 1';
client.HSET(hash, field, 33);
client.hincrby(hash, field, 10, helper.isNumber(43, done));
});
it('increments a key that has not been set', function (done) {
var field = 'field 2';
client.HINCRBY(hash, field, 10, helper.isNumber(10, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/hlen.spec.js 0000664 0000000 0000000 00000002164 13620024551 0020543 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'hlen' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('reports the count of keys', function (done) {
var hash = 'test hash';
var field1 = new Buffer('0123456789');
var value1 = new Buffer('abcdefghij');
var field2 = new Buffer(0);
var value2 = new Buffer(0);
client.HSET(hash, field1, value1, helper.isNumber(1));
client.HSET(hash, field2, value2, helper.isNumber(1));
client.HLEN(hash, helper.isNumber(2, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/hmget.spec.js 0000664 0000000 0000000 00000005562 13620024551 0020726 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'hmget' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
var hash = 'test hash';
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('error', done);
client.once('ready', function () {
client.flushdb();
client.HMSET(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'}, helper.isString('OK', done));
});
});
it('allows keys to be specified using multiple arguments', function (done) {
client.hmget(hash, '0123456789', 'some manner of key', function (err, reply) {
assert.strictEqual('abcdefghij', reply[0].toString());
assert.strictEqual('a type of value', reply[1].toString());
return done(err);
});
});
it('allows keys to be specified by passing an array without manipulating the array', function (done) {
var data = ['0123456789', 'some manner of key'];
client.HMGET(hash, data, function (err, reply) {
assert.strictEqual(data.length, 2);
assert.strictEqual('abcdefghij', reply[0].toString());
assert.strictEqual('a type of value', reply[1].toString());
return done(err);
});
});
it('allows keys to be specified by passing an array as first argument', function (done) {
client.HMGET([hash, '0123456789', 'some manner of key'], function (err, reply) {
assert.strictEqual('abcdefghij', reply[0].toString());
assert.strictEqual('a type of value', reply[1].toString());
return done(err);
});
});
it('allows a single key to be specified in an array', function (done) {
client.HMGET(hash, ['0123456789'], function (err, reply) {
assert.strictEqual('abcdefghij', reply[0].toString());
return done(err);
});
});
it('allows keys to be specified that have not yet been set', function (done) {
client.HMGET(hash, 'missing thing', 'another missing thing', function (err, reply) {
assert.strictEqual(null, reply[0]);
assert.strictEqual(null, reply[1]);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/hmset.spec.js 0000664 0000000 0000000 00000011437 13620024551 0020740 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'hmset' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
var hash = 'test hash';
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('handles redis-style syntax', function (done) {
client.HMSET(hash, '0123456789', 'abcdefghij', 'some manner of key', 'a type of value', 'otherTypes', 555, helper.isString('OK'));
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['0123456789'], 'abcdefghij');
assert.equal(obj['some manner of key'], 'a type of value');
return done(err);
});
});
it('handles object-style syntax', function (done) {
client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}, helper.isString('OK'));
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['0123456789'], 'abcdefghij');
assert.equal(obj['some manner of key'], 'a type of value');
return done(err);
});
});
it('handles object-style syntax and the key being a number', function (done) {
client.HMSET(231232, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}, undefined);
client.HGETALL(231232, function (err, obj) {
assert.equal(obj['0123456789'], 'abcdefghij');
assert.equal(obj['some manner of key'], 'a type of value');
return done(err);
});
});
it('allows a numeric key', function (done) {
client.HMSET(hash, 99, 'banana', helper.isString('OK'));
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['99'], 'banana');
return done(err);
});
});
it('allows a numeric key without callback', function (done) {
client.HMSET(hash, 99, 'banana', 'test', 25);
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['99'], 'banana');
assert.equal(obj.test, '25');
return done(err);
});
});
it('allows an array without callback', function (done) {
client.HMSET([hash, 99, 'banana', 'test', 25]);
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['99'], 'banana');
assert.equal(obj.test, '25');
return done(err);
});
});
it('allows an array and a callback', function (done) {
client.HMSET([hash, 99, 'banana', 'test', 25], helper.isString('OK'));
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['99'], 'banana');
assert.equal(obj.test, '25');
return done(err);
});
});
it('allows a key plus array without callback', function (done) {
client.HMSET(hash, [99, 'banana', 'test', 25]);
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['99'], 'banana');
assert.equal(obj.test, '25');
return done(err);
});
});
it('allows a key plus array and a callback', function (done) {
client.HMSET(hash, [99, 'banana', 'test', 25], helper.isString('OK'));
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['99'], 'banana');
assert.equal(obj.test, '25');
return done(err);
});
});
it('handles object-style syntax without callback', function (done) {
client.HMSET(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'});
client.HGETALL(hash, function (err, obj) {
assert.equal(obj['0123456789'], 'abcdefghij');
assert.equal(obj['some manner of key'], 'a type of value');
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/hset.spec.js 0000664 0000000 0000000 00000006056 13620024551 0020564 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'hset' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
var hash = 'test hash';
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('allows a value to be set in a hash', function (done) {
var field = new Buffer('0123456789');
var value = new Buffer('abcdefghij');
client.hset(hash, field, value, helper.isNumber(1));
client.HGET(hash, field, helper.isString(value.toString(), done));
});
it('handles an empty value', function (done) {
var field = new Buffer('0123456789');
var value = new Buffer(0);
client.HSET(hash, field, value, helper.isNumber(1));
client.HGET([hash, field], helper.isString('', done));
});
it('handles empty key and value', function (done) {
var field = new Buffer(0);
var value = new Buffer(0);
client.HSET([hash, field, value], function (err, res) {
assert.strictEqual(res, 1);
client.HSET(hash, field, value, helper.isNumber(0, done));
});
});
it('errors if someone passed a array either as field or as value', function (done) {
var hash = 'test hash';
var field = 'array';
var value = ['array contents'];
try {
client.HMSET(hash, field, value);
} catch (error) {
assert(/node_redis: The HMSET command contains a invalid argument type./.test(error.message));
done();
}
});
it('does not error when a buffer and date are set as values on the same hash', function (done) {
var hash = 'test hash';
var field1 = 'buffer';
var value1 = new Buffer('abcdefghij');
var field2 = 'date';
var value2 = new Date();
client.HMSET(hash, field1, value1, field2, value2, helper.isString('OK', done));
});
it('does not error when a buffer and date are set as fields on the same hash', function (done) {
var hash = 'test hash';
var value1 = 'buffer';
var field1 = new Buffer('abcdefghij');
var value2 = 'date';
var field2 = new Date();
client.HMSET(hash, field1, value1, field2, value2, helper.isString('OK', done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/incr.spec.js 0000664 0000000 0000000 00000007222 13620024551 0020550 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'incr' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
describe('when connected and a value in Redis', function () {
var client;
var key = 'ABOVE_SAFE_JAVASCRIPT_INTEGER';
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // Backwards compatible
afterEach(function () {
client.end(true);
});
/*
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 === 9007199254740991
9007199254740992 -> 9007199254740992
9007199254740993 -> 9007199254740992
9007199254740994 -> 9007199254740994
9007199254740995 -> 9007199254740996
9007199254740996 -> 9007199254740996
9007199254740997 -> 9007199254740996
...
*/
it('count above the safe integers as numbers', function (done) {
client = redis.createClient.apply(null, args);
// Set a value to the maximum safe allowed javascript number (2^53) - 1
client.set(key, MAX_SAFE_INTEGER, helper.isNotError());
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 1));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 2));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 3));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 4));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 5));
client.INCR(key, function (err, res) {
helper.isNumber(MAX_SAFE_INTEGER + 6)(err, res);
assert.strictEqual(typeof res, 'number');
});
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 7));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 8));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 9));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 10, done));
});
it('count above the safe integers as strings', function (done) {
args[2].string_numbers = true;
client = redis.createClient.apply(null, args);
// Set a value to the maximum safe allowed javascript number (2^53)
client.set(key, MAX_SAFE_INTEGER, helper.isNotError());
client.incr(key, helper.isString('9007199254740992'));
client.incr(key, helper.isString('9007199254740993'));
client.incr(key, helper.isString('9007199254740994'));
client.incr(key, helper.isString('9007199254740995'));
client.incr(key, helper.isString('9007199254740996'));
client.incr(key, function (err, res) {
helper.isString('9007199254740997')(err, res);
assert.strictEqual(typeof res, 'string');
});
client.incr(key, helper.isString('9007199254740998'));
client.incr(key, helper.isString('9007199254740999'));
client.incr(key, helper.isString('9007199254741000'));
client.incr(key, helper.isString('9007199254741001', done));
});
});
});
});
});
node-redis-3.0.2/test/commands/info.spec.js 0000664 0000000 0000000 00000005721 13620024551 0020552 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'info' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushall(done);
});
});
afterEach(function () {
client.end(true);
});
it('update serverInfo after a info command', function (done) {
client.set('foo', 'bar');
client.info();
client.select(2, function () {
assert.strictEqual(client.serverInfo.db2, undefined);
});
client.set('foo', 'bar');
client.info();
setTimeout(function () {
assert.strictEqual(typeof client.serverInfo.db2, 'object');
done();
}, 30);
});
it('works with optional section provided with and without callback', function (done) {
client.set('foo', 'bar');
client.info('keyspace');
client.select(2, function () {
assert.strictEqual(Object.keys(client.server_info).length, 2, 'Key length should be three');
assert.strictEqual(typeof client.server_info.db0, 'object', 'db0 keyspace should be an object');
});
client.info(['keyspace']);
client.set('foo', 'bar');
client.info('all', function (err, res) {
assert(Object.keys(client.server_info).length > 3, 'Key length should be way above three');
assert.strictEqual(typeof client.server_info.redis_version, 'string');
assert.strictEqual(typeof client.server_info.db2, 'object');
done();
});
});
it('check redis v.2.4 support', function (done) {
var end = helper.callFuncAfter(done, 2);
client.internal_send_command = function (command_obj) {
assert.strictEqual(command_obj.args.length, 0);
assert.strictEqual(command_obj.command, 'info');
end();
};
client.info();
client.info(function () {});
});
it('emit error after a failure', function (done) {
client.info();
client.once('error', function (err) {
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
assert.strictEqual(err.command, 'INFO');
done();
});
client.stream.destroy();
});
});
});
});
node-redis-3.0.2/test/commands/keys.spec.js 0000664 0000000 0000000 00000004562 13620024551 0020574 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var crypto = require('crypto');
var helper = require('../helper');
var redis = config.redis;
describe("The 'keys' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushall(done);
});
});
it('returns matching keys', function (done) {
client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2'], helper.isString('OK'));
client.KEYS('test keys*', function (err, results) {
assert.strictEqual(2, results.length);
assert.ok(~results.indexOf('test keys 1'));
assert.ok(~results.indexOf('test keys 2'));
return done(err);
});
});
it('handles a large packet size', function (done) {
var keys_values = [];
for (var i = 0; i < 200; i++) {
var key_value = [
'multibulk:' + crypto.randomBytes(256).toString('hex'), // use long strings as keys to ensure generation of large packet
'test val ' + i
];
keys_values.push(key_value);
}
client.mset(keys_values.reduce(function (a, b) {
return a.concat(b);
}), helper.isString('OK'));
client.keys('multibulk:*', function (err, results) {
assert.deepEqual(keys_values.map(function (val) {
return val[0];
}).sort(), results.sort());
return done(err);
});
});
it('handles an empty response', function (done) {
client.KEYS(['users:*'], function (err, results) {
assert.strictEqual(results.length, 0);
assert.ok(Array.isArray(results));
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/mget.spec.js 0000664 0000000 0000000 00000006271 13620024551 0020554 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'mget' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('error', done);
client.once('ready', function () {
client.flushdb();
client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'], done);
});
});
it('handles fetching multiple keys in argument form', function (done) {
client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'], helper.isString('OK'));
client.MGET('mget keys 1', 'mget keys 2', 'mget keys 3', function (err, results) {
assert.strictEqual(3, results.length);
assert.strictEqual('mget val 1', results[0].toString());
assert.strictEqual('mget val 2', results[1].toString());
assert.strictEqual('mget val 3', results[2].toString());
return done(err);
});
});
it('handles fetching multiple keys via an array', function (done) {
client.mget(['mget keys 1', 'mget keys 2', 'mget keys 3'], function (err, results) {
assert.strictEqual('mget val 1', results[0].toString());
assert.strictEqual('mget val 2', results[1].toString());
assert.strictEqual('mget val 3', results[2].toString());
return done(err);
});
});
it('handles fetching multiple keys, when some keys do not exist', function (done) {
client.MGET('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3'], function (err, results) {
assert.strictEqual(4, results.length);
assert.strictEqual('mget val 1', results[0].toString());
assert.strictEqual(null, results[1]);
assert.strictEqual('mget val 2', results[2].toString());
assert.strictEqual('mget val 3', results[3].toString());
return done(err);
});
});
it('handles fetching multiple keys, when some keys do not exist promisified', function () {
return client.MGETAsync('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3']).then(function (results) {
assert.strictEqual(4, results.length);
assert.strictEqual('mget val 1', results[0].toString());
assert.strictEqual(null, results[1]);
assert.strictEqual('mget val 2', results[2].toString());
assert.strictEqual('mget val 3', results[3].toString());
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/monitor.spec.js 0000664 0000000 0000000 00000021507 13620024551 0021306 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var utils = require('../../lib/utils');
var redis = config.redis;
describe("The 'monitor' method", function () {
helper.allTests(function (ip, args) {
var client;
afterEach(function () {
client.end(true);
});
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('connect', function () {
client.flushdb(done);
});
});
it('monitors commands on all redis clients and works in the correct order', function (done) {
var monitorClient = redis.createClient.apply(null, args);
var responses = [
['mget', 'some', 'keys', 'foo', 'bar'],
['set', 'json', '{"foo":"123","bar":"sdflkdfsjk","another":false}'],
['eval', "return redis.call('set', 'sha', 'test')", '0'],
['set', 'sha', 'test'],
['get', 'baz'],
['set', 'foo', 'bar" "s are " " good!"'],
['mget', 'foo', 'baz'],
['subscribe', 'foo', 'baz']
];
var end = helper.callFuncAfter(done, 5);
monitorClient.set('foo', 'bar');
monitorClient.flushdb();
monitorClient.monitor(function (err, res) {
assert.strictEqual(res, 'OK');
client.mget('some', 'keys', 'foo', 'bar');
client.set('json', JSON.stringify({
foo: '123',
bar: 'sdflkdfsjk',
another: false
}));
client.eval("return redis.call('set', 'sha', 'test')", 0);
monitorClient.get('baz', function (err, res) {
assert.strictEqual(res, null);
end(err);
});
monitorClient.set('foo', 'bar" "s are " " good!"', function (err, res) {
assert.strictEqual(res, 'OK');
end(err);
});
monitorClient.mget('foo', 'baz', function (err, res) {
assert.strictEqual(res[0], 'bar" "s are " " good!"');
assert.strictEqual(res[1], null);
end(err);
});
monitorClient.subscribe('foo', 'baz', function (err, res) {
// The return value might change in v.4
// assert.strictEqual(res, 'baz');
// TODO: Fix the return value of subscribe calls
end(err);
});
});
monitorClient.on('monitor', function (time, args, rawOutput) {
assert.strictEqual(monitorClient.monitoring, true);
assert.deepEqual(args, responses.shift());
assert(utils.monitor_regex.test(rawOutput), rawOutput);
if (responses.length === 0) {
monitorClient.quit(end);
}
});
});
it('monitors returns strings in the rawOutput even with return_buffers activated', function (done) {
if (process.platform === 'win32') {
this.skip();
}
var monitorClient = redis.createClient({
return_buffers: true,
path: '/tmp/redis.sock'
});
monitorClient.MONITOR(function (err, res) {
assert.strictEqual(monitorClient.monitoring, true);
assert.strictEqual(res.inspect(), new Buffer('OK').inspect());
monitorClient.mget('hello', new Buffer('world'));
});
monitorClient.on('monitor', function (time, args, rawOutput) {
assert.strictEqual(typeof rawOutput, 'string');
assert(utils.monitor_regex.test(rawOutput), rawOutput);
assert.deepEqual(args, ['mget', 'hello', 'world']);
// Quit immediatly ends monitoring mode and therefore does not stream back the quit command
monitorClient.quit(done);
});
});
it('monitors reconnects properly and works with the offline queue', function (done) {
var called = false;
client.MONITOR(helper.isString('OK'));
client.mget('hello', 'world');
client.on('monitor', function (time, args, rawOutput) {
assert.strictEqual(client.monitoring, true);
assert(utils.monitor_regex.test(rawOutput), rawOutput);
assert.deepEqual(args, ['mget', 'hello', 'world']);
if (called) {
// End after a reconnect
return done();
}
client.stream.destroy();
client.mget('hello', 'world');
called = true;
});
});
it('monitors reconnects properly and works with the offline queue in a batch statement', function (done) {
var called = false;
var multi = client.batch();
multi.MONITOR(helper.isString('OK'));
multi.mget('hello', 'world');
multi.exec(function (err, res) {
assert.deepEqual(res, ['OK', [null, null]]);
});
client.on('monitor', function (time, args, rawOutput) {
assert.strictEqual(client.monitoring, true);
assert(utils.monitor_regex.test(rawOutput), rawOutput);
assert.deepEqual(args, ['mget', 'hello', 'world']);
if (called) {
// End after a reconnect
return done();
}
client.stream.destroy();
client.mget('hello', 'world');
called = true;
});
});
it('monitor activates even if the command could not be processed properly after a reconnect', function (done) {
client.MONITOR(function (err, res) {
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
});
client.on('error', function (err) {}); // Ignore error here
client.stream.destroy();
var end = helper.callFuncAfter(done, 2);
client.on('monitor', function (time, args, rawOutput) {
assert.strictEqual(client.monitoring, true);
end();
});
client.on('reconnecting', function () {
client.get('foo', function (err, res) {
assert(!err);
assert.strictEqual(client.monitoring, true);
end();
});
});
});
it('monitors works in combination with the pub sub mode and the offline queue', function (done) {
var responses = [
['subscribe', '/foo', '/bar'],
['unsubscribe', '/bar'],
['get', 'foo'],
['subscribe', '/foo'],
['subscribe', 'baz'],
['unsubscribe', 'baz'],
['publish', '/foo', 'hello world']
];
var pub = redis.createClient();
pub.on('ready', function () {
client.MONITOR(function (err, res) {
assert.strictEqual(res, 'OK');
pub.get('foo', helper.isNull());
});
client.subscribe('/foo', '/bar');
client.unsubscribe('/bar');
setTimeout(function () {
client.stream.destroy();
client.once('ready', function () {
pub.publish('/foo', 'hello world');
});
client.set('foo', 'bar', helper.isError());
client.subscribe('baz');
client.unsubscribe('baz');
}, 150);
var called = false;
client.on('monitor', function (time, args, rawOutput) {
assert.deepEqual(args, responses.shift());
assert(utils.monitor_regex.test(rawOutput), rawOutput);
if (responses.length === 0) {
// The publish is called right after the reconnect and the monitor is called before the message is emitted.
// Therefore we have to wait till the next tick
process.nextTick(function () {
assert(called);
client.quit(done);
pub.end(false);
});
}
});
client.on('message', function (channel, msg) {
assert.strictEqual(channel, '/foo');
assert.strictEqual(msg, 'hello world');
called = true;
});
});
});
});
});
node-redis-3.0.2/test/commands/mset.spec.js 0000664 0000000 0000000 00000010342 13620024551 0020562 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var uuid = require('uuid');
describe("The 'mset' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var key, value, key2, value2;
beforeEach(function () {
key = uuid.v4();
value = uuid.v4();
key2 = uuid.v4();
value2 = uuid.v4();
});
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('reports an error', function (done) {
client.mset(key, value, key2, value2, function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
done();
});
});
afterEach(function () {
client.end(true);
});
describe('and a callback is specified', function () {
describe('with valid parameters', function () {
it('sets the value correctly', function (done) {
client.mset(key, value, key2, value2, function (err) {
if (err) {
return done(err);
}
client.get(key, helper.isString(value));
client.get(key2, helper.isString(value2, done));
});
});
});
describe("with undefined 'key' parameter and missing 'value' parameter", function () {
it('reports an error', function (done) {
client.mset(undefined, function (err, res) {
helper.isError()(err, null);
done();
});
});
});
});
describe('and no callback is specified', function () {
describe('with valid parameters', function () {
it('sets the value correctly', function (done) {
client.mset(key, value2, key2, value);
client.get(key, helper.isString(value2));
client.get(key2, helper.isString(value, done));
});
it('sets the value correctly with array syntax', function (done) {
client.mset([key, value2, key2, value]);
client.get(key, helper.isString(value2));
client.get(key2, helper.isString(value, done));
});
});
describe("with undefined 'key' and missing 'value' parameter", function () {
// this behavior is different from the 'set' behavior.
it('emits an error', function (done) {
client.on('error', function (err) {
assert.strictEqual(err.message, "ERR wrong number of arguments for 'mset' command");
assert.strictEqual(err.name, 'ReplyError');
done();
});
client.mset();
});
});
});
});
});
});
});
node-redis-3.0.2/test/commands/msetnx.spec.js 0000664 0000000 0000000 00000002410 13620024551 0021125 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'msetnx' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('if any keys exist entire operation fails', function (done) {
client.mset(['mset1', 'val1', 'mset2', 'val2', 'mset3', 'val3'], helper.isString('OK'));
client.MSETNX(['mset3', 'val3', 'mset4', 'val4'], helper.isNumber(0));
client.exists(['mset4'], helper.isNumber(0, done));
});
it('sets multiple keys if all keys are not set', function (done) {
client.msetnx(['mset3', 'val3', 'mset4', 'val4'], helper.isNumber(1));
client.exists(['mset3'], helper.isNumber(1));
client.exists(['mset3'], helper.isNumber(1, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/randomkey.test.js 0000664 0000000 0000000 00000002016 13620024551 0021627 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'randomkey' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns a random key', function (done) {
client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2'], helper.isString('OK'));
client.RANDOMKEY([], function (err, results) {
assert.strictEqual(true, /test keys.+/.test(results));
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/rename.spec.js 0000664 0000000 0000000 00000002243 13620024551 0021062 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'rename' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('populates the new key', function (done) {
client.set(['foo', 'bar'], helper.isString('OK'));
client.rename(['foo', 'new foo'], helper.isString('OK'));
client.exists(['new foo'], helper.isNumber(1, done));
});
it('removes the old key', function (done) {
client.set(['foo', 'bar'], helper.isString('OK'));
client.RENAME(['foo', 'new foo'], helper.isString('OK'));
client.exists(['foo'], helper.isNumber(0, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/renamenx.spec.js 0000664 0000000 0000000 00000002573 13620024551 0021436 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'renamenx' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('renames the key if target does not yet exist', function (done) {
client.set('foo', 'bar', helper.isString('OK'));
client.RENAMENX('foo', 'foo2', helper.isNumber(1));
client.exists('foo', helper.isNumber(0));
client.exists(['foo2'], helper.isNumber(1, done));
});
it('does not rename the key if the target exists', function (done) {
client.set('foo', 'bar', helper.isString('OK'));
client.set('foo2', 'apple', helper.isString('OK'));
client.renamenx('foo', 'foo2', helper.isNumber(0));
client.exists('foo', helper.isNumber(1));
client.exists(['foo2'], helper.isNumber(1, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/rpush.spec.js 0000664 0000000 0000000 00000002042 13620024551 0020751 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var assert = require('assert');
describe("The 'rpush' command", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('inserts multiple values at a time into a list', function (done) {
client.rpush('test', ['list key', 'should be a list']);
client.lrange('test', 0, -1, function (err, res) {
assert.equal(res[0], 'list key');
assert.equal(res[1], 'should be a list');
done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sadd.spec.js 0000664 0000000 0000000 00000004425 13620024551 0020532 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sadd' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('allows a single value to be added to the set', function (done) {
client.SADD('set0', 'member0', helper.isNumber(1));
client.smembers('set0', function (err, res) {
assert.ok(~res.indexOf('member0'));
return done(err);
});
});
it('does not add the same value to the set twice', function (done) {
client.sadd('set0', 'member0', helper.isNumber(1));
client.SADD('set0', 'member0', helper.isNumber(0, done));
});
it('allows multiple values to be added to the set', function (done) {
client.sadd('set0', ['member0', 'member1', 'member2'], helper.isNumber(3));
client.smembers('set0', function (err, res) {
assert.strictEqual(res.length, 3);
assert.ok(~res.indexOf('member0'));
assert.ok(~res.indexOf('member1'));
assert.ok(~res.indexOf('member2'));
return done(err);
});
});
it('allows multiple values to be added to the set with a different syntax', function (done) {
client.sadd(['set0', 'member0', 'member1', 'member2'], helper.isNumber(3));
client.smembers('set0', function (err, res) {
assert.strictEqual(res.length, 3);
assert.ok(~res.indexOf('member0'));
assert.ok(~res.indexOf('member1'));
assert.ok(~res.indexOf('member2'));
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/scard.spec.js 0000664 0000000 0000000 00000001511 13620024551 0020704 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'scard' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns the number of values in a set', function (done) {
client.sadd('foo', [1, 2, 3], helper.isNumber(3));
client.scard('foo', helper.isNumber(3, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/script.spec.js 0000664 0000000 0000000 00000003543 13620024551 0021123 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var crypto = require('crypto');
var helper = require('../helper');
var redis = config.redis;
describe("The 'script' method", function () {
helper.allTests(function (ip, args) {
var command = 'return 99';
var commandSha = crypto.createHash('sha1').update(command).digest('hex');
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
client.end(true);
});
it("loads script with client.script('load')", function (done) {
client.script('load', command, function (err, result) {
assert.strictEqual(result, commandSha);
return done();
});
});
it('allows a loaded script to be evaluated', function (done) {
client.evalsha(commandSha, 0, helper.isNumber(99, done));
});
it('allows a script to be loaded as part of a chained transaction', function (done) {
client.multi().script('load', command).exec(function (err, result) {
assert.strictEqual(result[0], commandSha);
return done();
});
});
it("allows a script to be loaded using a transaction's array syntax", function (done) {
client.multi([['script', 'load', command]]).exec(function (err, result) {
assert.strictEqual(result[0], commandSha);
return done();
});
});
});
});
});
node-redis-3.0.2/test/commands/sdiff.spec.js 0000664 0000000 0000000 00000002714 13620024551 0020711 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sdiff' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns set difference', function (done) {
client.sadd('foo', 'x', helper.isNumber(1));
client.sadd('foo', ['a'], helper.isNumber(1));
client.sadd('foo', 'b', helper.isNumber(1));
client.sadd(['foo', 'c'], helper.isNumber(1));
client.sadd(['bar', 'c', helper.isNumber(1)]);
client.sadd('baz', 'a', helper.isNumber(1));
client.sadd('baz', 'd', helper.isNumber(1));
client.sdiff('foo', 'bar', 'baz', function (err, values) {
values.sort();
assert.equal(values.length, 2);
assert.equal(values[0], 'b');
assert.equal(values[1], 'x');
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sdiffstore.spec.js 0000664 0000000 0000000 00000002744 13620024551 0021771 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sdiffstore' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('calculates set difference ands stores it in a key', function (done) {
client.sadd('foo', 'x', helper.isNumber(1));
client.sadd('foo', 'a', helper.isNumber(1));
client.sadd('foo', 'b', helper.isNumber(1));
client.sadd('foo', 'c', helper.isNumber(1));
client.sadd('bar', 'c', helper.isNumber(1));
client.sadd('baz', 'a', helper.isNumber(1));
client.sadd('baz', 'd', helper.isNumber(1));
client.sdiffstore('quux', 'foo', 'bar', 'baz', helper.isNumber(2));
client.smembers('quux', function (err, values) {
var members = values.sort();
assert.deepEqual(members, [ 'b', 'x' ]);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/select.spec.js 0000664 0000000 0000000 00000013104 13620024551 0021070 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'select' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('returns an error if redis is not connected', function (done) {
var buffering = client.select(1, function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
assert(typeof buffering === 'boolean');
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
client.end(true);
});
it('changes the database and calls the callback', function (done) {
// default value of null means database 0 will be used.
assert.strictEqual(client.selected_db, undefined, 'default db should be undefined');
var buffering = client.SELECT(1, function (err, res) {
helper.isNotError()(err, res);
assert.strictEqual(client.selected_db, 1, 'db should be 1 after select');
done();
});
assert(typeof buffering === 'boolean');
});
describe('and a callback is specified', function () {
describe('with a valid db index', function () {
it('selects the appropriate database', function (done) {
assert.strictEqual(client.selected_db, undefined, 'default db should be undefined');
client.select(1, function (err) {
assert.equal(err, null);
assert.equal(client.selected_db, 1, 'we should have selected the new valid DB');
done();
});
});
});
describe('with an invalid db index', function () {
it('returns an error', function (done) {
assert.strictEqual(client.selected_db, undefined, 'default db should be undefined');
client.select(9999, function (err) {
assert.equal(err.code, 'ERR');
assert((err.message === 'ERR DB index is out of range' || err.message === 'ERR invalid DB index'));
done();
});
});
});
});
describe('and no callback is specified', function () {
describe('with a valid db index', function () {
it('selects the appropriate database', function (done) {
assert.strictEqual(client.selected_db, undefined, 'default db should be undefined');
client.select(1);
setTimeout(function () {
assert.equal(client.selected_db, 1, 'we should have selected the new valid DB');
done();
}, 25);
});
});
describe('with an invalid db index', function () {
it('emits an error when callback not provided', function (done) {
assert.strictEqual(client.selected_db, undefined, 'default db should be undefined');
client.on('error', function (err) {
assert.strictEqual(err.command, 'SELECT');
assert((err.message === 'ERR DB index is out of range' || err.message === 'ERR invalid DB index'));
done();
});
client.select(9999);
});
});
});
describe('reconnection occurs', function () {
it('selects the appropriate database after a reconnect', function (done) {
assert.strictEqual(client.selected_db, undefined, 'default db should be undefined');
client.select(3);
client.set('foo', 'bar', function () {
client.stream.destroy();
});
client.once('ready', function () {
assert.strictEqual(client.selected_db, 3);
assert(typeof client.server_info.db3 === 'object');
done();
});
});
});
});
});
});
});
node-redis-3.0.2/test/commands/set.spec.js 0000664 0000000 0000000 00000017303 13620024551 0020411 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
var uuid = require('uuid');
describe("The 'set' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var key, value;
beforeEach(function () {
key = uuid.v4();
value = uuid.v4();
});
describe('when not connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit();
});
client.on('end', done);
});
it('reports an error', function (done) {
client.set(key, value, function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
});
});
describe('when connected', function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
client.end(true);
});
describe('and a callback is specified', function () {
describe('with valid parameters', function () {
it('sets the value correctly', function (done) {
client.SET(key, value, function (err, res) {
helper.isNotError()(err, res);
client.get(key, function (err, res) {
helper.isString(value)(err, res);
done();
});
});
});
it('set expire date in seconds', function (done) {
client.set('foo', 'bar', 'ex', 10, helper.isString('OK'));
client.pttl('foo', function (err, res) {
assert(res >= 10000 - 50); // Max 50 ms should have passed
assert(res <= 10000); // Max possible should be 10.000
done(err);
});
});
it('set expire date in milliseconds', function (done) {
client.set('foo', 'bar', 'px', 100, helper.isString('OK'));
client.pttl('foo', function (err, res) {
assert(res >= 50); // Max 50 ms should have passed
assert(res <= 100); // Max possible should be 100
done(err);
});
});
it('only set the key if (not) already set', function (done) {
client.set('foo', 'bar', 'NX', helper.isString('OK'));
client.set('foo', 'bar', 'nx', helper.isNull());
client.set('foo', 'bar', 'EX', '10', 'XX', helper.isString('OK'));
client.ttl('foo', function (err, res) {
assert(res >= 9); // Min 9s should be left
assert(res <= 10); // Max 10s should be left
done(err);
});
});
});
describe('reports an error with invalid parameters', function () {
it("undefined 'key' and missing 'value' parameter", function (done) {
client.set(undefined, function (err, res) {
helper.isError()(err, null);
assert.equal(err.command, 'SET');
done();
});
});
it('empty array as second parameter', function (done) {
client.set('foo', [], function (err, res) {
assert.strictEqual(err.message, "ERR wrong number of arguments for 'set' command");
done();
});
});
});
});
describe('and no callback is specified', function () {
describe('with valid parameters', function () {
it('sets the value correctly', function (done) {
client.set(key, value);
client.get(key, helper.isString(value, done));
});
it('sets the value correctly even if the callback is explicitly set to undefined', function (done) {
client.set(key, value, undefined);
client.get(key, helper.isString(value, done));
});
it('sets the value correctly with the array syntax', function (done) {
client.set([key, value]);
client.get(key, helper.isString(value, done));
});
});
describe("with undefined 'key' and missing 'value' parameter", function () {
it('emits an error without callback', function (done) {
client.on('error', function (err) {
assert.equal(err.message, "ERR wrong number of arguments for 'set' command");
assert.equal(err.command, 'SET');
done();
});
client.set(undefined);
});
});
it('errors if null value is passed', function (done) {
try {
client.set('foo', null);
assert(false);
} catch (error) {
assert(/The SET command contains a invalid argument type./.test(error.message));
}
client.get('foo', helper.isNull(done));
});
it('calls callback with error if null value is passed', function (done) {
client.set('foo', null, helper.isError(done));
});
it('emit an error with only the key set', function (done) {
client.on('error', function (err) {
assert.equal(err.message, "ERR wrong number of arguments for 'set' command");
done();
});
client.set('foo');
});
it('emit an error without any parameters', function (done) {
client.once('error', function (err) {
assert.equal(err.message, "ERR wrong number of arguments for 'set' command");
assert.equal(err.command, 'SET');
done();
});
client.set();
});
});
});
});
});
});
node-redis-3.0.2/test/commands/setex.spec.js 0000664 0000000 0000000 00000002131 13620024551 0020737 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'setex' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('sets a key with an expiry', function (done) {
client.setex(['setex key', '100', 'setex val'], helper.isString('OK'));
var buffering = client.exists(['setex key'], helper.isNumber(1));
assert(typeof buffering === 'boolean');
client.ttl(['setex key'], function (err, ttl) {
assert(ttl > 0);
return done();
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/setnx.spec.js 0000664 0000000 0000000 00000002162 13620024551 0020754 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'setnx' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('sets key if it does not have a value', function (done) {
client.SETNX('foo', 'banana', helper.isNumber(1));
client.get('foo', helper.isString('banana', done));
});
it('does not set key if it already has a value', function (done) {
client.set('foo', 'bar', helper.isString('OK'));
client.setnx('foo', 'banana', helper.isNumber(0));
client.get('foo', helper.isString('bar', done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sinter.spec.js 0000664 0000000 0000000 00000004311 13620024551 0021115 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sinter' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('handles two sets being intersected', function (done) {
client.sadd('sa', 'a', helper.isNumber(1));
client.sadd('sa', 'b', helper.isNumber(1));
client.sadd('sa', 'c', helper.isNumber(1));
client.sadd('sb', 'b', helper.isNumber(1));
client.sadd('sb', 'c', helper.isNumber(1));
client.sadd('sb', 'd', helper.isNumber(1));
client.SINTER('sa', 'sb', function (err, intersection) {
assert.equal(intersection.length, 2);
assert.deepEqual(intersection.sort(), [ 'b', 'c' ]);
return done(err);
});
});
it('handles three sets being intersected', function (done) {
client.sadd('sa', 'a', helper.isNumber(1));
client.sadd('sa', 'b', helper.isNumber(1));
client.sadd('sa', 'c', helper.isNumber(1));
client.sadd('sb', 'b', helper.isNumber(1));
client.sadd('sb', 'c', helper.isNumber(1));
client.sadd('sb', 'd', helper.isNumber(1));
client.sadd('sc', 'c', helper.isNumber(1));
client.sadd('sc', 'd', helper.isNumber(1));
client.sadd('sc', 'e', helper.isNumber(1));
client.sinter('sa', 'sb', 'sc', function (err, intersection) {
assert.equal(intersection.length, 1);
assert.equal(intersection[0], 'c');
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sinterstore.spec.js 0000664 0000000 0000000 00000003036 13620024551 0022175 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sinterstore' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('calculates set intersection and stores it in a key', function (done) {
client.sadd('sa', 'a', helper.isNumber(1));
client.sadd('sa', 'b', helper.isNumber(1));
client.sadd('sa', 'c', helper.isNumber(1));
client.sadd('sb', 'b', helper.isNumber(1));
client.sadd('sb', 'c', helper.isNumber(1));
client.sadd('sb', 'd', helper.isNumber(1));
client.sadd('sc', 'c', helper.isNumber(1));
client.sadd('sc', 'd', helper.isNumber(1));
client.sadd('sc', 'e', helper.isNumber(1));
client.sinterstore('foo', 'sa', 'sb', 'sc', helper.isNumber(1));
client.smembers('foo', function (err, members) {
assert.deepEqual(members, [ 'c' ]);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sismember.spec.js 0000664 0000000 0000000 00000002004 13620024551 0021574 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sismember' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns 0 if the value is not in the set', function (done) {
client.sismember('foo', 'banana', helper.isNumber(0, done));
});
it('returns 1 if the value is in the set', function (done) {
client.sadd('foo', 'banana', helper.isNumber(1));
client.SISMEMBER('foo', 'banana', helper.isNumber(1, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/slowlog.spec.js 0000664 0000000 0000000 00000002542 13620024551 0021303 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'slowlog' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('logs operations in slowlog', function (done) {
client.config('set', 'slowlog-log-slower-than', 0, helper.isString('OK'));
client.slowlog('reset', helper.isString('OK'));
client.set('foo', 'bar', helper.isString('OK'));
client.get('foo', helper.isString('bar'));
client.SLOWLOG('get', function (err, res) {
assert.equal(res.length, 3);
assert.equal(res[0][3].length, 2);
assert.deepEqual(res[1][3], ['set', 'foo', 'bar']);
assert.deepEqual(res[2][3], ['slowlog', 'reset']);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/smembers.spec.js 0000664 0000000 0000000 00000002166 13620024551 0021434 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'smembers' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns all values in a set', function (done) {
client.sadd('foo', 'x', helper.isNumber(1));
client.sadd('foo', 'y', helper.isNumber(1));
client.smembers('foo', function (err, values) {
assert.equal(values.length, 2);
var members = values.sort();
assert.deepEqual(members, [ 'x', 'y' ]);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/smove.spec.js 0000664 0000000 0000000 00000002531 13620024551 0020744 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'smove' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('moves a value to a set that does not yet exist', function (done) {
client.sadd('foo', 'x', helper.isNumber(1));
client.smove('foo', 'bar', 'x', helper.isNumber(1));
client.sismember('foo', 'x', helper.isNumber(0));
client.sismember('bar', 'x', helper.isNumber(1, done));
});
it('does not move a value if it does not exist in the first set', function (done) {
client.sadd('foo', 'x', helper.isNumber(1));
client.SMOVE('foo', 'bar', 'y', helper.isNumber(0));
client.sismember('foo', 'y', helper.isNumber(0));
client.sismember('bar', 'y', helper.isNumber(0, done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sort.spec.js 0000664 0000000 0000000 00000011321 13620024551 0020577 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
function setupData (client, done) {
client.rpush('y', 'd');
client.rpush('y', 'b');
client.rpush('y', 'a');
client.rpush('y', 'c');
client.rpush('x', '3');
client.rpush('x', '9');
client.rpush('x', '2');
client.rpush('x', '4');
client.set('w3', '4');
client.set('w9', '5');
client.set('w2', '12');
client.set('w4', '6');
client.set('o2', 'buz');
client.set('o3', 'foo');
client.set('o4', 'baz');
client.set('o9', 'bar');
client.set('p2', 'qux');
client.set('p3', 'bux');
client.set('p4', 'lux');
client.set('p9', 'tux', done);
}
describe("The 'sort' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('error', done);
client.once('connect', function () {
client.flushdb();
setupData(client, done);
});
});
describe('alphabetical', function () {
it('sorts in ascending alphabetical order', function (done) {
client.sort('y', 'asc', 'alpha', function (err, sorted) {
assert.deepEqual(sorted, ['a', 'b', 'c', 'd']);
return done(err);
});
});
it('sorts in descending alphabetical order', function (done) {
client.SORT('y', 'desc', 'alpha', function (err, sorted) {
assert.deepEqual(sorted, ['d', 'c', 'b', 'a']);
return done(err);
});
});
});
describe('numeric', function () {
it('sorts in ascending numeric order', function (done) {
client.sort('x', 'asc', function (err, sorted) {
assert.deepEqual(sorted, [2, 3, 4, 9]);
return done(err);
});
});
it('sorts in descending numeric order', function (done) {
client.sort('x', 'desc', function (err, sorted) {
assert.deepEqual(sorted, [9, 4, 3, 2]);
return done(err);
});
});
});
describe('pattern', function () {
it('handles sorting with a pattern', function (done) {
client.sort('x', 'by', 'w*', 'asc', function (err, sorted) {
assert.deepEqual(sorted, [3, 9, 4, 2]);
return done(err);
});
});
it("handles sorting with a 'by' pattern and 1 'get' pattern", function (done) {
client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', function (err, sorted) {
assert.deepEqual(sorted, ['foo', 'bar', 'baz', 'buz']);
return done(err);
});
});
it("handles sorting with a 'by' pattern and 2 'get' patterns", function (done) {
client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', function (err, sorted) {
assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']);
return done(err);
});
});
it("handles sorting with a 'by' pattern and 2 'get' patterns with the array syntax", function (done) {
client.sort(['x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*'], function (err, sorted) {
assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']);
return done(err);
});
});
it("sorting with a 'by' pattern and 2 'get' patterns and stores results", function (done) {
client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon', function (err) {
if (err) return done(err);
});
client.lrange('bacon', 0, -1, function (err, values) {
assert.deepEqual(values, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']);
return done(err);
});
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/spop.spec.js 0000664 0000000 0000000 00000002130 13620024551 0020567 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'spop' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns a random element from the set', function (done) {
client.sadd('zzz', 'member0', helper.isNumber(1));
client.scard('zzz', helper.isNumber(1));
client.spop('zzz', function (err, value) {
if (err) return done(err);
assert.equal(value, 'member0');
client.scard('zzz', helper.isNumber(0, done));
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/srem.spec.js 0000664 0000000 0000000 00000005310 13620024551 0020557 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'srem' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('removes a value', function (done) {
client.sadd('set0', 'member0', helper.isNumber(1));
client.srem('set0', 'member0', helper.isNumber(1));
client.scard('set0', helper.isNumber(0, done));
});
it('handles attempting to remove a missing value', function (done) {
client.SREM('set0', 'member0', helper.isNumber(0, done));
});
it('allows multiple values to be removed', function (done) {
client.sadd('set0', ['member0', 'member1', 'member2'], helper.isNumber(3));
client.SREM('set0', ['member1', 'member2'], helper.isNumber(2));
client.smembers('set0', function (err, res) {
assert.strictEqual(res.length, 1);
assert.ok(~res.indexOf('member0'));
return done(err);
});
});
it('allows multiple values to be removed with send_command', function (done) {
client.send_command('sadd', ['set0', 'member0', 'member1', 'member2'], helper.isNumber(3));
client.send_command('srem', ['set0', 'member1', 'member2'], helper.isNumber(2));
client.smembers('set0', function (err, res) {
assert.strictEqual(res.length, 1);
assert.ok(~res.indexOf('member0'));
return done(err);
});
});
it('handles a value missing from the set of values being removed', function (done) {
client.sadd(['set0', 'member0', 'member1', 'member2'], helper.isNumber(3));
client.SREM(['set0', 'member3', 'member4'], helper.isNumber(0));
client.smembers('set0', function (err, res) {
assert.strictEqual(res.length, 3);
assert.ok(~res.indexOf('member0'));
assert.ok(~res.indexOf('member1'));
assert.ok(~res.indexOf('member2'));
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sunion.spec.js 0000664 0000000 0000000 00000002727 13620024551 0021135 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sunion' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns the union of a group of sets', function (done) {
client.sadd('sa', 'a', helper.isNumber(1));
client.sadd('sa', 'b', helper.isNumber(1));
client.sadd('sa', 'c', helper.isNumber(1));
client.sadd('sb', 'b', helper.isNumber(1));
client.sadd('sb', 'c', helper.isNumber(1));
client.sadd('sb', 'd', helper.isNumber(1));
client.sadd('sc', 'c', helper.isNumber(1));
client.sadd('sc', 'd', helper.isNumber(1));
client.sadd('sc', 'e', helper.isNumber(1));
client.sunion('sa', 'sb', 'sc', function (err, union) {
assert.deepEqual(union.sort(), ['a', 'b', 'c', 'd', 'e']);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/sunionstore.spec.js 0000664 0000000 0000000 00000003126 13620024551 0022204 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'sunionstore' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('stores the result of a union', function (done) {
client.sadd('sa', 'a', helper.isNumber(1));
client.sadd('sa', 'b', helper.isNumber(1));
client.sadd('sa', 'c', helper.isNumber(1));
client.sadd('sb', 'b', helper.isNumber(1));
client.sadd('sb', 'c', helper.isNumber(1));
client.sadd('sb', 'd', helper.isNumber(1));
client.sadd('sc', 'c', helper.isNumber(1));
client.sadd('sc', 'd', helper.isNumber(1));
client.sadd('sc', 'e', helper.isNumber(1));
client.sunionstore('foo', 'sa', 'sb', 'sc', helper.isNumber(5));
client.smembers('foo', function (err, members) {
assert.equal(members.length, 5);
assert.deepEqual(members.sort(), ['a', 'b', 'c', 'd', 'e']);
return done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/ttl.spec.js 0000664 0000000 0000000 00000002062 13620024551 0020415 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'ttl' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('returns the current ttl on a key', function (done) {
client.set(['ttl key', 'ttl val'], helper.isString('OK'));
client.expire(['ttl key', '100'], helper.isNumber(1));
client.TTL(['ttl key'], function (err, ttl) {
assert(ttl >= 99);
assert(ttl <= 100);
done(err);
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/type.spec.js 0000664 0000000 0000000 00000003625 13620024551 0020601 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'type' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('reports string type', function (done) {
client.set(['string key', 'should be a string'], helper.isString('OK'));
client.TYPE(['string key'], helper.isString('string', done));
});
it('reports list type', function (done) {
client.rpush(['list key', 'should be a list'], helper.isNumber(1));
client.type(['list key'], helper.isString('list', done));
});
it('reports set type', function (done) {
client.sadd(['set key', 'should be a set'], helper.isNumber(1));
client.TYPE(['set key'], helper.isString('set', done));
});
it('reports zset type', function (done) {
client.zadd('zset key', ['10.0', 'should be a zset'], helper.isNumber(1));
client.TYPE(['zset key'], helper.isString('zset', done));
});
it('reports hash type', function (done) {
client.hset('hash key', 'hashtest', 'should be a hash', helper.isNumber(1));
client.TYPE(['hash key'], helper.isString('hash', done));
});
it('reports none for null key', function (done) {
client.TYPE('not here yet', helper.isString('none', done));
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/watch.spec.js 0000664 0000000 0000000 00000003273 13620024551 0020725 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('../lib/config');
var helper = require('../helper');
var redis = config.redis;
describe("The 'watch' method", function () {
helper.allTests(function (ip, args) {
var watched = 'foobar';
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
client.end(true);
});
it('does not execute transaction if watched key was modified prior to execution', function (done) {
client.WATCH(watched);
client.incr(watched);
var multi = client.multi();
multi.incr(watched);
multi.exec(helper.isNull(done));
});
it('successfully modifies other keys independently of transaction', function (done) {
client.set('unwatched', 200);
client.set(watched, 0);
client.watch(watched);
client.incr(watched);
client.multi().incr(watched).exec(function (err, replies) {
assert.strictEqual(replies, null, 'Aborted transaction multi-bulk reply should be null.');
client.get('unwatched', function (err, reply) {
assert.equal(reply, 200, 'Expected 200, got ' + reply);
return done(err);
});
});
});
});
});
});
node-redis-3.0.2/test/commands/zadd.spec.js 0000664 0000000 0000000 00000003407 13620024551 0020540 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var assert = require('assert');
var redis = config.redis;
describe("The 'zadd' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('reports an error', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.zadd('infinity', [+'5t', 'should not be possible'], helper.isError(done));
});
it('return inf / -inf', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
helper.serverVersionAtLeast.call(this, client, [3, 0, 2]);
client.zadd('infinity', [+Infinity, 'should be inf'], helper.isNumber(1));
client.zadd('infinity', ['inf', 'should be also be inf'], helper.isNumber(1));
client.zadd('infinity', -Infinity, 'should be negative inf', helper.isNumber(1));
client.zadd('infinity', [99999999999999999999999, 'should not be inf'], helper.isNumber(1));
client.zrange('infinity', 0, -1, 'WITHSCORES', function (err, res) {
assert.equal(res[5], 'inf');
assert.equal(res[1], '-inf');
assert.equal(res[3], '9.9999999999999992e+22');
done();
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/zscan.spec.js 0000664 0000000 0000000 00000003072 13620024551 0020732 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var assert = require('assert');
var redis = config.redis;
describe("The 'zscan' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('return values', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
helper.serverVersionAtLeast.call(this, client, [2, 8, 0]);
var hash = {};
var set = [];
var zset = ['zset:1'];
for (var i = 0; i < 500; i++) {
hash['key_' + i] = 'value_' + i;
set.push('member_' + i);
zset.push(i, 'z_member_' + i);
}
client.hmset('hash:1', hash);
client.sadd('set:1', set);
client.zadd(zset);
client.zscan('zset:1', 0, 'MATCH', '*', 'COUNT', 500, function (err, res) {
assert(!err);
assert.strictEqual(res.length, 2);
assert.strictEqual(res[1].length, 1000);
done();
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/commands/zscore.spec.js 0000664 0000000 0000000 00000001715 13620024551 0021123 0 ustar 00root root 0000000 0000000 'use strict';
var config = require('../lib/config');
var helper = require('../helper');
var assert = require('assert');
var redis = config.redis;
describe("The 'zscore' method", function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client;
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(done);
});
});
it('should return the score of member in the sorted set at key', function (done) {
client.zadd('myzset', 1, 'one');
client.zscore('myzset', 'one', function (err, res) {
assert.equal(res, 1);
done();
});
});
afterEach(function () {
client.end(true);
});
});
});
});
node-redis-3.0.2/test/conect.slave.spec.js 0000664 0000000 0000000 00000005610 13620024551 0020377 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var RedisProcess = require('./lib/redis-process');
var rp;
var path = require('path');
var redis = config.redis;
if (process.platform === 'win32') {
// TODO: Fix redis process spawn on windows
return;
}
describe('master slave sync', function () {
var master = null;
var slave = null;
before(function (done) {
helper.stopRedis(function () {
helper.startRedis('./conf/password.conf', done);
});
});
before(function (done) {
if (helper.redisProcess().spawnFailed()) return done();
master = redis.createClient({
password: 'porkchopsandwiches'
});
var multi = master.multi();
var i = 0;
while (i < 1000) {
i++;
// Write some data in the redis instance, so there's something to sync
multi.set('foo' + i, 'bar' + new Array(500).join(Math.random()));
}
multi.exec(done);
});
it('sync process and no master should delay ready being emitted for slaves', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var port = 6381;
var firstInfo;
slave = redis.createClient({
port: port,
retry_strategy: function (options) {
// Try to reconnect in very small intervals to catch the master_link_status down before the sync completes
return 10;
}
});
var tmp = slave.info.bind(slave);
var i = 0;
slave.info = function (err, res) {
i++;
tmp(err, res);
if (!firstInfo || Object.keys(firstInfo).length === 0) {
firstInfo = slave.server_info;
}
};
slave.on('connect', function () {
assert.strictEqual(i, 0);
});
var end = helper.callFuncAfter(done, 2);
slave.on('ready', function () {
assert.strictEqual(this.server_info.master_link_status, 'up');
assert.strictEqual(firstInfo.master_link_status, 'down');
assert(i > 1);
this.get('foo300', function (err, res) {
assert.strictEqual(res.substr(0, 3), 'bar');
end(err);
});
});
RedisProcess.start(function (err, _rp) {
rp = _rp;
end(err);
}, path.resolve(__dirname, './conf/slave.conf'), port);
});
after(function (done) {
if (helper.redisProcess().spawnFailed()) return done();
var end = helper.callFuncAfter(done, 3);
rp.stop(end);
slave.end(true);
master.flushdb(function (err) {
end(err);
master.end(true);
});
helper.stopRedis(function () {
helper.startRedis('./conf/redis.conf', end);
});
});
});
node-redis-3.0.2/test/conf/ 0000775 0000000 0000000 00000000000 13620024551 0015447 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/test/conf/faulty.cert 0000664 0000000 0000000 00000002117 13620024551 0017633 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa
MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q
XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V
5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6
+v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd06nW5nQlUA6wVt1JjlLPwBwYsWLsi
YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4
tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/
5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr
LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ
v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi
wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo
d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc
2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm
uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6
w4kLcP8=
-----END CERTIFICATE-----
node-redis-3.0.2/test/conf/password.conf 0000664 0000000 0000000 00000000152 13620024551 0020156 0 ustar 00root root 0000000 0000000 requirepass porkchopsandwiches
port 6379
bind ::1 127.0.0.1
unixsocket /tmp/redis.sock
unixsocketperm 700
node-redis-3.0.2/test/conf/redis.conf 0000664 0000000 0000000 00000000113 13620024551 0017417 0 ustar 00root root 0000000 0000000 port 6379
bind ::1 127.0.0.1
unixsocket /tmp/redis.sock
unixsocketperm 700
node-redis-3.0.2/test/conf/redis.js.org.cert 0000664 0000000 0000000 00000002117 13620024551 0020636 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa
MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q
XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V
5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6
+v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd05nW5nQlUA6wVt1JjlLPwBwYsWLsi
YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4
tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/
5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr
LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ
v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi
wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo
d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc
2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm
uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6
w4kLcP8=
-----END CERTIFICATE-----
node-redis-3.0.2/test/conf/redis.js.org.key 0000664 0000000 0000000 00000003213 13620024551 0020467 0 ustar 00root root 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAn8OYxMkd/uTKynEjUD8mY5z4oj2/ERw3mqfsiJm4Y3uCETjd
DepcHYZSQwC5bU86SYznKLdK5PVRcNH/gaTsqVscoyzfsBXo+AldfqhfTO7XYtMB
PtXlvI2ti16FKjjaXXXzx0YQwIZpp/RwOe1zsDdpbRm3Osm37am7fVRq2XZIQkIy
trr6/o7Rz1ocwXVwCsp5rAapH6MfDq9cVlBtR3TmdbmdCVQDrBW3UmOUs/AHBixY
uyJhDEwLw2qmAAiDm1QtIKkrDHmKwv8J6i1VUYM1n8bxHWdOvG7KA+VzdCySPnuG
ZXi1krgknFAWLv/16G+MC8eiEqsQ96pXlcvztwIDAQABAoIBAGx4kLCLHCKDlGv+
hMtnFNltKiJ9acxkLByFBsN4GwjwQk8PHIbmJ8Sj/hYf18WvlRN65zdtuxvYs4K2
EZQkNcqGYdsoDHexaIt/UEs+ZfYF85bVTHMtJt3uE3Ycpq0UDK6H9wvFNnqAyBuQ
iuHJplJuTNYWL6Fqc8aZBwMA3crmwWTelgS+IXLH06E298+KIxbYrWSgrbcmV/Pj
Iwek4CPS0apoJnXxbZDDhAEYGOTxDNXGm+r7BaX/ePM2x1PPib2X9F2XqFV+A4T8
Z91axKJwMrVuTrJkaLPDx9lNUskvvV6KgjZAtYRGpLQTN1AqXJZ09IoK9sNPE4rX
9fm4awECgYEAzMJkABL0UOoGJhdRf/R0aUOQMO7vYetX5SK9QXcEI04XYFieSaPm
71st+R/JlJ+LhrTrzGXvyU0tFAQaQZtwaGj/JhbptIpLlGrVf3mqSvxkNi/wzQnn
jBJrrf1ZkDiqtSy7AxGAefWblgK3R1ZU5+0a5jubDkmOltIlbULf0skCgYEAx76l
+5KhWOJPvrjNGB1a8oVXiFzoCpaVVZIhSdl0AtvkKollm5Ou+CKYpE3fKrejRXTD
zmr5bJFXT3VlmIa010cgXJ2btlFa1RiNzgretsOmMcHxLkpAu2/a0L4psHlCrWVK
fxbUW0BYEFVXBDe/4JhFw41YqohdPkFAyo5OUn8CgYBQZGYkzUxVVHzTicY66bym
85ryS217UY5x7WDHCjZ6shdlgYWsPgjWo0L6k+tuSfHbEr+dwcwSihWPzUiNx7yr
kcXTq51YgA/KluN6KEefJ1clG099AU2C5lyWtGjswgLsHULTopSBzdenXyuce53c
bXBpQq/PPTwZpSqCqoX8WQKBgGe+nsk+jGz1BoRBycyHmrAyD5e04ZR2R9PtFTsd
JYNCoIxzVoHqv8sDdRKJm6q9PKEbl4PDzg7UomuTxxPki1LxD17rQW/9a1cY7LYi
sTBuCAj5+YGYcWypGRaoXlDZeodC/+Fogx1uGw9Is+xt5EwL6tg5tt7D+uIV1Egg
h4+TAoGBAKYA/jn9v93bzPi+w1rlZrlPufRSr4k3mcHae165N/1PnjSguTFIF5DW
+1f5S+XioNyTcfx5gKI8f6wRn1j5zbB24GXgu8dXCzRHC2gzrwq2D9v1od4zP/o7
xFxyiNGOMUJ7uW9d/nEL5Eg4CQKZEkZNmzHhuKNr8wDSr16DhXVK
-----END RSA PRIVATE KEY-----
node-redis-3.0.2/test/conf/rename.conf 0000664 0000000 0000000 00000000366 13620024551 0017572 0 ustar 00root root 0000000 0000000 port 6379
bind ::1 127.0.0.1
unixsocket /tmp/redis.sock
unixsocketperm 700
rename-command SET 807081f5afa96845a02816a28b7258c3
rename-command GET f397808a43ceca3963e22b4e13deb672
rename-command GETRANGE 9e3102b15cf231c4e9e940f284744fe0
node-redis-3.0.2/test/conf/slave.conf 0000664 0000000 0000000 00000000204 13620024551 0017424 0 ustar 00root root 0000000 0000000 port 6381
bind ::1 127.0.0.1
unixsocket /tmp/redis6381.sock
unixsocketperm 700
slaveof localhost 6379
masterauth porkchopsandwiches
node-redis-3.0.2/test/conf/stunnel.conf.template 0000664 0000000 0000000 00000000406 13620024551 0021620 0 ustar 00root root 0000000 0000000 pid = __dirname/stunnel.pid
; output = __dirname/stunnel.log
CAfile = __dirname/redis.js.org.cert
cert = __dirname/redis.js.org.cert
key = __dirname/redis.js.org.key
client = no
foreground = yes
debug = 7
[redis]
accept = 127.0.0.1:6380
connect = 127.0.0.1:6379
node-redis-3.0.2/test/connection.spec.js 0000664 0000000 0000000 00000072233 13620024551 0020157 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
var intercept = require('intercept-stdout');
var net = require('net');
var client;
describe('connection tests', function () {
beforeEach(function () {
client = null;
});
afterEach(function () {
client.end(true);
});
it('unofficially support for a private stream', function () {
// While using a private stream, reconnection and other features are not going to work properly.
// Besides that some functions also have to be monkey patched to be safe from errors in this case.
// Therefore this is not officially supported!
var socket = new net.Socket();
client = new redis.RedisClient({
prefix: 'test'
}, socket);
assert.strictEqual(client.stream, socket);
assert.strictEqual(client.stream.listeners('error').length, 1);
assert.strictEqual(client.address, '"Private stream"');
// Pretent a reconnect event
client.create_stream();
assert.strictEqual(client.stream, socket);
assert.strictEqual(client.stream.listeners('error').length, 1);
});
describe('quit on lost connections', function () {
it('calling quit while the connection is down should not end in reconnecting version a', function (done) {
var called = 0;
client = redis.createClient({
port: 9999,
retry_strategy: function (options) {
var bool = client.quit(function (err, res) {
assert.strictEqual(res, 'OK');
assert.strictEqual(err, null);
assert.strictEqual(called++, -1);
setTimeout(done, 25);
});
assert.strictEqual(bool, false);
assert.strictEqual(called++, 0);
return 5;
}
});
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.');
called = -1;
});
});
it('calling quit while the connection is down should not end in reconnecting version b', function (done) {
var called = false;
client = redis.createClient(9999);
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.');
called = true;
});
var bool = client.quit(function (err, res) {
assert.strictEqual(res, 'OK');
assert.strictEqual(err, null);
assert(called);
done();
});
assert.strictEqual(bool, false);
});
it('calling quit while the connection is down without offline queue should end the connection right away', function (done) {
var called = false;
client = redis.createClient(9999, {
enable_offline_queue: false
});
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is not yet established and the offline queue is deactivated.');
called = true;
});
var bool = client.quit(function (err, res) {
assert.strictEqual(res, 'OK');
assert.strictEqual(err, null);
assert(called);
done();
});
// TODO: In v.3 the quit command would be fired right away, so bool should be true
assert.strictEqual(bool, false);
});
it('calling quit while connected without offline queue should end the connection when all commands have finished', function (done) {
var called = false;
client = redis.createClient({
enable_offline_queue: false
});
client.on('ready', function () {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(res, 'OK');
called = true;
});
var bool = client.quit(function (err, res) {
assert.strictEqual(res, 'OK');
assert.strictEqual(err, null);
assert(called);
done();
});
// TODO: In v.3 the quit command would be fired right away, so bool should be true
assert.strictEqual(bool, true);
});
});
it('do not quit before connected or a connection issue is detected', function (done) {
client = redis.createClient();
client.set('foo', 'bar', helper.isString('OK'));
var bool = client.quit(done);
assert.strictEqual(bool, false);
});
it('quit "succeeds" even if the client connection is closed while doing so', function (done) {
client = redis.createClient();
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(res, 'OK');
client.quit(function (err, res) {
assert.strictEqual(res, 'OK');
done(err);
});
client.end(true); // Flushing the quit command should result in a success
});
});
it('quit right away if connection drops while quit command is on the fly', function (done) {
client = redis.createClient();
client.once('ready', function () {
client.set('foo', 'bar', helper.isError());
var bool = client.quit(done);
assert.strictEqual(bool, true);
process.nextTick(function () {
client.stream.destroy();
});
});
});
});
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
describe('on lost connection', function () {
it('emit an error after max retry timeout and do not try to reconnect afterwards', function (done) {
// TODO: Investigate why this test fails with windows. Reconnect is only triggered once
if (process.platform === 'win32') this.skip();
var connect_timeout = 600; // in ms
client = redis.createClient({
connect_timeout: connect_timeout
});
var time = 0;
client.once('ready', function () {
helper.killConnection(client);
});
client.on('reconnecting', function (params) {
time += params.delay;
});
client.on('error', function (err) {
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
process.nextTick(function () { // End is called after the error got emitted
assert.strictEqual(client.emitted_end, true);
assert.strictEqual(client.connected, false);
assert.strictEqual(client.ready, false);
assert.strictEqual(client.closing, true);
assert.strictEqual(client.retry_totaltime, connect_timeout);
assert.strictEqual(time, connect_timeout);
done();
});
}
});
});
it('end connection while retry is still ongoing', function (done) {
var connect_timeout = 1000; // in ms
client = redis.createClient({
connect_timeout: connect_timeout
});
client.once('ready', function () {
helper.killConnection(client);
});
client.on('reconnecting', function (params) {
client.end(true);
assert.strictEqual(params.times_connected, 1);
setTimeout(done, 5);
});
});
it('can not connect with wrong host / port in the options object', function (done) {
var options = {
host: 'somewhere',
port: 6379,
family: ip,
max_attempts: 1
};
client = redis.createClient(options);
assert.strictEqual(client.connection_options.family, ip === 'IPv6' ? 6 : 4);
assert.strictEqual(Object.keys(options).length, 4);
var end = helper.callFuncAfter(done, 2);
client.on('error', function (err) {
assert(/CONNECTION_BROKEN|ENOTFOUND|EAI_AGAIN/.test(err.code));
end();
});
});
it('emits error once if reconnecting after command has been executed but not yet returned without callback', function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
client.set('foo', 'bar', function (err) {
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
done();
});
// Abort connection before the value returned
client.stream.destroy();
});
});
it('retryStrategy used to reconnect with individual error', function (done) {
client = redis.createClient({
retryStrategy: function (options) {
if (options.totalRetryTime > 150) {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Redis connection in broken state: retry aborted.');
assert.strictEqual(err.origin.message, 'Connection timeout');
done();
});
// Pass a individual error message to the error handler
return new Error('Connection timeout');
}
return Math.min(options.attempt * 25, 200);
},
port: 9999
});
});
it('retry_strategy used to reconnect', function (done) {
client = redis.createClient({
retry_strategy: function (options) {
if (options.total_retry_time > 150) {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Redis connection in broken state: retry aborted.');
assert.strictEqual(err.code, 'CONNECTION_BROKEN');
assert.strictEqual(err.origin.code, 'ECONNREFUSED');
done();
});
return false;
}
return Math.min(options.attempt * 25, 200);
},
port: 9999
});
});
it('retryStrategy used to reconnect with defaults', function (done) {
var unhookIntercept = intercept(function () {
return '';
});
redis.debugMode = true;
client = redis.createClient({
retryStrategy: function (options) {
client.set('foo', 'bar');
assert(redis.debugMode);
return null;
}
});
setTimeout(function () {
client.stream.destroy();
}, 50);
client.on('error', function (err) {
if (err instanceof redis.AbortError) {
assert.strictEqual(err.message, 'Redis connection in broken state: retry aborted.');
assert.strictEqual(err.code, 'CONNECTION_BROKEN');
unhookIntercept();
redis.debugMode = false;
done();
}
});
});
});
describe('when not connected', function () {
it('emit an error after the socket timeout exceeded the connect_timeout time', function (done) {
var connect_timeout = 500; // in ms
client = redis.createClient({
// Auto detect ipv4 and use non routable ip to trigger the timeout
host: '10.255.255.1',
connect_timeout: connect_timeout
});
process.nextTick(function () {
assert.strictEqual(client.stream.listeners('timeout').length, 1);
});
assert.strictEqual(client.address, '10.255.255.1:6379');
assert.strictEqual(client.connection_options.family, 4);
client.on('reconnecting', function (params) {
throw new Error('No reconnect, since no connection was ever established');
});
var time = Date.now();
client.on('error', function (err) {
if (err.code === 'ENETUNREACH') { // The test is run without a internet connection. Pretent it works
return done();
}
assert(/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message), err.message);
// The code execution on windows is very slow at times
var add = process.platform !== 'win32' ? 15 : 200;
var now = Date.now();
assert(now - time < connect_timeout + add, 'The real timeout time should be below ' + (connect_timeout + add) + 'ms but is: ' + (now - time));
// Timers sometimes trigger early (e.g. 1ms to early)
assert(now - time >= connect_timeout - 5, 'The real timeout time should be above ' + connect_timeout + 'ms, but it is: ' + (now - time));
done();
});
});
it('use the system socket timeout if the connect_timeout has not been provided', function (done) {
client = redis.createClient({
host: '2001:db8::ff00:42:8329' // auto detect ip v6
});
assert.strictEqual(client.address, '2001:db8::ff00:42:8329:6379');
assert.strictEqual(client.connection_options.family, 6);
process.nextTick(function () {
assert.strictEqual(client.stream.listeners('timeout').length, 0);
done();
});
});
it('clears the socket timeout after a connection has been established', function (done) {
client = redis.createClient({
connect_timeout: 1000
});
process.nextTick(function () {
// node > 6
var timeout = client.stream.timeout;
// node <= 6
if (timeout === undefined) timeout = client.stream._idleTimeout;
assert.strictEqual(timeout, 1000);
});
client.on('connect', function () {
// node > 6
var expected = 0;
var timeout = client.stream.timeout;
// node <= 6
if (timeout === undefined) {
timeout = client.stream._idleTimeout;
expected = -1;
}
assert.strictEqual(timeout, expected);
assert.strictEqual(client.stream.listeners('timeout').length, 0);
client.on('ready', done);
});
});
it('connect with host and port provided in the options object', function (done) {
client = redis.createClient({
host: 'localhost',
port: '6379',
connect_timeout: 1000
});
client.once('ready', done);
});
it('connect with path provided in the options object', function (done) {
if (process.platform === 'win32') {
this.skip();
}
client = redis.createClient({
path: '/tmp/redis.sock',
connect_timeout: 1000
});
var end = helper.callFuncAfter(done, 2);
client.once('ready', end);
client.set('foo', 'bar', end);
});
it('connects correctly with args', function (done) {
client = redis.createClient.apply(null, args);
client.on('error', done);
client.once('ready', function () {
client.removeListener('error', done);
client.get('recon 1', done);
});
});
it('connects correctly with default values', function (done) {
client = redis.createClient();
client.on('error', done);
client.once('ready', function () {
client.removeListener('error', done);
client.get('recon 1', done);
});
});
it('connects with a port only', function (done) {
client = redis.createClient(6379);
assert.strictEqual(client.connection_options.family, 4);
client.on('error', done);
client.once('ready', function () {
client.removeListener('error', done);
client.get('recon 1', done);
});
});
it('connects correctly to localhost', function (done) {
client = redis.createClient(null, null);
client.on('error', done);
client.once('ready', function () {
client.removeListener('error', done);
client.get('recon 1', done);
});
});
it('connects correctly to the provided host with the port set to null', function (done) {
client = redis.createClient(null, 'localhost');
client.on('error', done);
assert.strictEqual(client.address, 'localhost:6379');
client.once('ready', function () {
client.set('foo', 'bar');
client.get('foo', function (err, res) {
assert.strictEqual(res, 'bar');
done(err);
});
});
});
it('connects correctly to localhost and no ready check', function (done) {
client = redis.createClient(undefined, undefined, {
no_ready_check: true
});
client.on('error', done);
client.once('ready', function () {
client.set('foo', 'bar');
client.get('foo', function (err, res) {
assert.strictEqual(res, 'bar');
done(err);
});
});
});
it('connects correctly to the provided host with the port set to undefined', function (done) {
client = redis.createClient(undefined, 'localhost', {
no_ready_check: true
});
client.on('error', done);
assert.strictEqual(client.address, 'localhost:6379');
client.once('ready', function () {
client.set('foo', 'bar');
client.get('foo', function (err, res) {
assert.strictEqual(res, 'bar');
done(err);
});
});
});
it('connects correctly even if the info command is not present on the redis server', function (done) {
client = redis.createClient.apply(null, args);
client.info = function (cb) {
// Mock the result
cb(new Error("ERR unknown command 'info'"));
};
client.once('ready', function () {
assert.strictEqual(Object.keys(client.server_info).length, 0);
done();
});
});
it('fake the stream to mock redis', function () {
// This is needed for libraries that want to mock the stream like fakeredis
var temp = redis.RedisClient.prototype.create_stream;
var create_stream_string = String(temp);
redis.RedisClient.prototype.create_stream = function () {
this.connected = true;
this.ready = true;
};
client = new redis.RedisClient();
assert.strictEqual(client.stream, undefined);
assert.strictEqual(client.ready, true);
assert.strictEqual(client.connected, true);
client.end = function () {};
assert(create_stream_string !== String(redis.RedisClient.prototype.create_stream));
redis.RedisClient.prototype.create_stream = temp;
assert(create_stream_string === String(redis.RedisClient.prototype.create_stream));
});
if (ip === 'IPv4') {
it('allows connecting with the redis url to the default host and port, select db 3 and warn about duplicate db option', function (done) {
client = redis.createClient('redis:///3?db=3');
assert.strictEqual(client.selected_db, '3');
client.on('ready', done);
});
it('allows connecting with the redis url and the default port and auth provided even though it is not required', function (done) {
client = redis.createClient('redis://:porkchopsandwiches@' + config.HOST[ip] + '/');
var end = helper.callFuncAfter(done, 2);
client.on('warning', function (msg) {
assert.strictEqual(msg, 'Warning: Redis server does not require a password, but a password was supplied.');
end();
});
client.on('ready', end);
});
it('allows connecting with the redis url as first parameter and the options as second parameter', function (done) {
client = redis.createClient('//127.0.0.1', {
connect_timeout: 1000
});
assert.strictEqual(client.options.connect_timeout, 1000);
client.on('ready', done);
});
it('allows connecting with the redis url in the options object and works with protocols other than the redis protocol (e.g. http)', function (done) {
client = redis.createClient({
url: 'http://foo:porkchopsandwiches@' + config.HOST[ip] + '/3'
});
assert.strictEqual(client.auth_pass, 'porkchopsandwiches');
assert.strictEqual(+client.selected_db, 3);
assert(!client.options.port);
assert.strictEqual(client.options.host, config.HOST[ip]);
client.on('ready', done);
});
it('allows connecting with the redis url and no auth and options as second parameter', function (done) {
var options = {
detect_buffers: false
};
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT, options);
assert.strictEqual(Object.keys(options).length, 1);
client.on('ready', done);
});
it('allows connecting with the redis url and no auth and options as third parameter', function (done) {
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT, null, {
detect_buffers: false
});
client.on('ready', done);
});
}
it('redis still loading <= 500', function (done) {
client = redis.createClient.apply(null, args);
var tmp = client.info.bind(client);
var end = helper.callFuncAfter(done, 3);
var delayed = false;
var time;
// Mock original function and pretent redis is still loading
client.info = function (cb) {
tmp(function (err, res) {
if (!delayed) {
assert(!err);
client.server_info.loading = 1;
client.server_info.loading_eta_seconds = 0.5;
delayed = true;
time = Date.now();
}
end();
cb(err, res);
});
};
client.on('ready', function () {
var rest = Date.now() - time;
assert(rest >= 495, 'Rest should be equal or above 500 ms but is: ' + rest); // setTimeout might trigger early
// Be on the safe side and accept 200ms above the original value
assert(rest - 250 < 500, 'Rest - 250 should be below 500 ms but is: ' + (rest - 250));
assert(delayed);
end();
});
});
it('redis still loading > 1000ms', function (done) {
client = redis.createClient.apply(null, args);
var tmp = client.info.bind(client);
var end = helper.callFuncAfter(done, 3);
var delayed = false;
var time;
// Mock original function and pretent redis is still loading
client.info = function (cb) {
tmp(function (err, res) {
if (!delayed) {
assert(!err);
// Try reconnecting after one second even if redis tells us the time needed is above one second
client.server_info.loading = 1;
client.server_info.loading_eta_seconds = 2.5;
delayed = true;
time = Date.now();
}
end();
cb(err, res);
});
};
client.on('ready', function () {
var rest = Date.now() - time;
assert(rest >= 998, '`rest` should be equal or above 1000 ms but is: ' + rest); // setTimeout might trigger early
// Be on the safe side and accept 200ms above the original value
assert(rest - 250 < 1000, '`rest` - 250 should be below 1000 ms but is: ' + (rest - 250));
assert(delayed);
end();
});
});
});
});
});
});
node-redis-3.0.2/test/custom_errors.spec.js 0000664 0000000 0000000 00000006461 13620024551 0020726 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var errors = require('../lib/customErrors');
describe('errors', function () {
describe('AbortError', function () {
it('should inherit from Error', function () {
var e = new errors.AbortError({});
assert.strictEqual(e.message, '');
assert.strictEqual(e.name, 'AbortError');
assert.strictEqual(Object.keys(e).length, 0);
assert(e instanceof Error);
assert(e instanceof errors.AbortError);
});
it('should list options properties but not name and message', function () {
var e = new errors.AbortError({
name: 'weird',
message: 'hello world',
property: true
});
assert.strictEqual(e.message, 'hello world');
assert.strictEqual(e.name, 'weird');
assert.strictEqual(e.property, true);
assert.strictEqual(Object.keys(e).length, 2);
assert(e instanceof Error);
assert(e instanceof errors.AbortError);
assert(delete e.name);
assert.strictEqual(e.name, 'AbortError');
});
it('should change name and message', function () {
var e = new errors.AbortError({
message: 'hello world',
property: true
});
assert.strictEqual(e.name, 'AbortError');
assert.strictEqual(e.message, 'hello world');
e.name = 'foo';
e.message = 'foobar';
assert.strictEqual(e.name, 'foo');
assert.strictEqual(e.message, 'foobar');
});
});
describe('AggregateError', function () {
it('should inherit from Error and AbortError', function () {
var e = new errors.AggregateError({});
assert.strictEqual(e.message, '');
assert.strictEqual(e.name, 'AggregateError');
assert.strictEqual(Object.keys(e).length, 0);
assert(e instanceof Error);
assert(e instanceof errors.AggregateError);
assert(e instanceof errors.AbortError);
});
it('should list options properties but not name and message', function () {
var e = new errors.AggregateError({
name: 'weird',
message: 'hello world',
property: true
});
assert.strictEqual(e.message, 'hello world');
assert.strictEqual(e.name, 'weird');
assert.strictEqual(e.property, true);
assert.strictEqual(Object.keys(e).length, 2);
assert(e instanceof Error);
assert(e instanceof errors.AggregateError);
assert(e instanceof errors.AbortError);
assert(delete e.name);
assert.strictEqual(e.name, 'AggregateError');
});
it('should change name and message', function () {
var e = new errors.AggregateError({
message: 'hello world',
property: true
});
assert.strictEqual(e.name, 'AggregateError');
assert.strictEqual(e.message, 'hello world');
e.name = 'foo';
e.message = 'foobar';
assert.strictEqual(e.name, 'foo');
assert.strictEqual(e.message, 'foobar');
});
});
});
node-redis-3.0.2/test/detect_buffers.spec.js 0000664 0000000 0000000 00000031405 13620024551 0021000 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
describe('detect_buffers', function () {
var client;
var args = config.configureClient('localhost', {
detect_buffers: true
});
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('error', done);
client.once('connect', function () {
client.flushdb(function (err) {
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2');
client.set('string key 1', 'string value');
return done(err);
});
});
});
afterEach(function () {
client.end(true);
});
describe('get', function () {
describe('first argument is a string', function () {
it('returns a string', function (done) {
client.get('string key 1', helper.isString('string value', done));
});
it('returns a string when executed as part of transaction', function (done) {
client.multi().get('string key 1').exec(function (err, res) {
helper.isString('string value', done)(err, res[0]);
});
});
});
describe('first argument is a buffer', function () {
it('returns a buffer', function (done) {
client.get(new Buffer('string key 1'), function (err, reply) {
assert.strictEqual(true, Buffer.isBuffer(reply));
assert.strictEqual('', reply.inspect());
return done(err);
});
});
it('returns a bufffer when executed as part of transaction', function (done) {
client.multi().get(new Buffer('string key 1')).exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]));
assert.strictEqual('', reply[0].inspect());
return done(err);
});
});
});
});
describe('multi.hget', function () {
it('can interleave string and buffer results', function (done) {
client.multi()
.hget('hash key 2', 'key 1')
.hget(new Buffer('hash key 2'), 'key 1')
.hget('hash key 2', new Buffer('key 2'))
.hget('hash key 2', 'key 2')
.exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(4, reply.length);
assert.strictEqual('val 1', reply[0]);
assert.strictEqual(true, Buffer.isBuffer(reply[1]));
assert.strictEqual('', reply[1].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[2]));
assert.strictEqual('', reply[2].inspect());
assert.strictEqual('val 2', reply[3]);
return done(err);
});
});
});
describe('batch.hget', function () {
it('can interleave string and buffer results', function (done) {
client.batch()
.hget('hash key 2', 'key 1')
.hget(new Buffer('hash key 2'), 'key 1')
.hget('hash key 2', new Buffer('key 2'))
.hget('hash key 2', 'key 2')
.exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(4, reply.length);
assert.strictEqual('val 1', reply[0]);
assert.strictEqual(true, Buffer.isBuffer(reply[1]));
assert.strictEqual('', reply[1].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[2]));
assert.strictEqual('', reply[2].inspect());
assert.strictEqual('val 2', reply[3]);
return done(err);
});
});
});
describe('hmget', function () {
describe('first argument is a string', function () {
it('returns strings for keys requested', function (done) {
client.hmget('hash key 2', 'key 1', 'key 2', function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(2, reply.length);
assert.strictEqual('val 1', reply[0]);
assert.strictEqual('val 2', reply[1]);
return done(err);
});
});
it('returns strings for keys requested in transaction', function (done) {
client.multi().hmget('hash key 2', 'key 1', 'key 2').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.strictEqual('val 1', reply[0][0]);
assert.strictEqual('val 2', reply[0][1]);
return done(err);
});
});
it('handles array of strings with undefined values (repro #344)', function (done) {
client.hmget('hash key 2', 'key 3', 'key 4', function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(2, reply.length);
assert.equal(null, reply[0]);
assert.equal(null, reply[1]);
return done(err);
});
});
it('handles array of strings with undefined values in transaction (repro #344)', function (done) {
client.multi().hmget('hash key 2', 'key 3', 'key 4').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.equal(null, reply[0][0]);
assert.equal(null, reply[0][1]);
return done(err);
});
});
});
describe('first argument is a buffer', function () {
it('returns buffers for keys requested', function (done) {
client.hmget(new Buffer('hash key 2'), 'key 1', 'key 2', function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(2, reply.length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]));
assert.strictEqual(true, Buffer.isBuffer(reply[1]));
assert.strictEqual('', reply[0].inspect());
assert.strictEqual('', reply[1].inspect());
return done(err);
});
});
it('returns buffers for keys requested in transaction', function (done) {
client.multi().hmget(new Buffer('hash key 2'), 'key 1', 'key 2').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.strictEqual(true, Buffer.isBuffer(reply[0][0]));
assert.strictEqual(true, Buffer.isBuffer(reply[0][1]));
assert.strictEqual('', reply[0][0].inspect());
assert.strictEqual('', reply[0][1].inspect());
return done(err);
});
});
it('returns buffers for keys requested in .batch', function (done) {
client.batch().hmget(new Buffer('hash key 2'), 'key 1', 'key 2').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.strictEqual(true, Buffer.isBuffer(reply[0][0]));
assert.strictEqual(true, Buffer.isBuffer(reply[0][1]));
assert.strictEqual('', reply[0][0].inspect());
assert.strictEqual('', reply[0][1].inspect());
return done(err);
});
});
});
});
describe('hgetall', function (done) {
describe('first argument is a string', function () {
it('returns string values', function (done) {
client.hgetall('hash key 2', function (err, reply) {
assert.strictEqual('object', typeof reply);
assert.strictEqual(2, Object.keys(reply).length);
assert.strictEqual('val 1', reply['key 1']);
assert.strictEqual('val 2', reply['key 2']);
return done(err);
});
});
it('returns string values when executed in transaction', function (done) {
client.multi().hgetall('hash key 2').exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual('val 1', reply[0]['key 1']);
assert.strictEqual('val 2', reply[0]['key 2']);
return done(err);
});
});
it('returns string values when executed in .batch', function (done) {
client.batch().hgetall('hash key 2').exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual('val 1', reply[0]['key 1']);
assert.strictEqual('val 2', reply[0]['key 2']);
return done(err);
});
});
});
describe('first argument is a buffer', function () {
it('returns buffer values', function (done) {
client.hgetall(new Buffer('hash key 2'), function (err, reply) {
assert.strictEqual(null, err);
assert.strictEqual('object', typeof reply);
assert.strictEqual(2, Object.keys(reply).length);
assert.strictEqual(true, Buffer.isBuffer(reply['key 1']));
assert.strictEqual(true, Buffer.isBuffer(reply['key 2']));
assert.strictEqual('', reply['key 1'].inspect());
assert.strictEqual('', reply['key 2'].inspect());
return done(err);
});
});
it('returns buffer values when executed in transaction', function (done) {
client.multi().hgetall(new Buffer('hash key 2')).exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 1']));
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']));
assert.strictEqual('', reply[0]['key 1'].inspect());
assert.strictEqual('', reply[0]['key 2'].inspect());
return done(err);
});
});
it('returns buffer values when executed in .batch', function (done) {
client.batch().hgetall(new Buffer('hash key 2')).exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 1']));
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']));
assert.strictEqual('', reply[0]['key 1'].inspect());
assert.strictEqual('', reply[0]['key 2'].inspect());
return done(err);
});
});
});
});
});
node-redis-3.0.2/test/good_traces.spec.js 0000664 0000000 0000000 00000003175 13620024551 0020310 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var fork = require('child_process').fork;
var redis = config.redis;
describe('stack traces', function () {
it('should return good traces with NODE_ENV=development set', function (done) {
var external = fork('./test/lib/good-traces.js', {
env: {
NODE_ENV: 'development'
}
});
var id = setTimeout(function () {
external.kill();
done(new Error('Timeout'));
}, 6000);
external.on('close', function (code) {
clearTimeout(id);
assert.strictEqual(code, 0);
done();
});
});
it('should return good traces with NODE_DEBUG=redis env set', function (done) {
var external = fork('./test/lib/good-traces.js', {
env: {
NODE_DEBUG: 'redis'
},
silent: true
});
var id = setTimeout(function () {
external.kill();
done(new Error('Timeout'));
}, 6000);
external.on('close', function (code) {
clearTimeout(id);
assert.strictEqual(code, 0);
done();
});
});
// This is always going to return good stack traces
it('should always return good stack traces for rejected offline commands', function (done) {
var client = redis.createClient({
enable_offline_queue: false
});
client.set('foo', function (err, res) {
assert(/good_traces.spec.js/.test(err.stack));
client.quit(done);
});
});
});
node-redis-3.0.2/test/helper.js 0000664 0000000 0000000 00000016752 13620024551 0016352 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var path = require('path');
var config = require('./lib/config');
var RedisProcess = require('./lib/redis-process');
var StunnelProcess = require('./lib/stunnel-process');
var rp;
var stunnel_process;
function startRedis (conf, done, port) {
RedisProcess.start(function (err, _rp) {
rp = _rp;
return done(err);
}, path.resolve(__dirname, conf), port);
}
// don't start redis every time we
// include this helper file!
if (!process.env.REDIS_TESTS_STARTED) {
process.env.REDIS_TESTS_STARTED = true;
before(function (done) {
startRedis('./conf/redis.conf', done);
});
after(function (done) {
if (rp) rp.stop(done);
});
}
function arrayHelper (results) {
if (results instanceof Array) {
assert.strictEqual(results.length, 1, 'The array length may only be one element');
return results[0];
}
return results;
}
module.exports = {
redisProcess: function () {
return rp;
},
stopRedis: function (done) {
rp.stop(done);
},
startRedis: startRedis,
stopStunnel: function (done) {
if (stunnel_process) {
StunnelProcess.stop(stunnel_process, done);
} else {
done();
}
},
startStunnel: function (done) {
StunnelProcess.start(function (err, _stunnel_process) {
stunnel_process = _stunnel_process;
return done(err);
}, path.resolve(__dirname, './conf'));
},
isNumber: function (expected, done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected ' + expected + ', got error: ' + err);
results = arrayHelper(results);
assert.strictEqual(results, expected, expected + ' !== ' + results);
assert.strictEqual(typeof results, 'number', 'expected a number, got ' + typeof results);
if (done) done();
};
},
isString: function (str, done) {
str = '' + str; // Make sure it's a string
return function (err, results) {
assert.strictEqual(err, null, "expected string '" + str + "', got error: " + err);
results = arrayHelper(results);
if (Buffer.isBuffer(results)) { // If options are passed to return either strings or buffers...
results = results.toString();
}
assert.strictEqual(results, str, str + ' does not match ' + results);
if (done) done();
};
},
isNull: function (done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected null, got error: ' + err);
results = arrayHelper(results);
assert.strictEqual(results, null, results + ' is not null');
if (done) done();
};
},
isUndefined: function (done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected null, got error: ' + err);
results = arrayHelper(results);
assert.strictEqual(results, undefined, results + ' is not undefined');
if (done) done();
};
},
isError: function (done) {
return function (err, results) {
assert(err instanceof Error, "err is not instance of 'Error', but an error is expected here.");
if (done) done();
};
},
isNotError: function (done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected success, got an error: ' + err);
if (done) done();
};
},
isType: {
number: function (done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected any number, got error: ' + err);
assert.strictEqual(typeof results, 'number', results + ' is not a number');
if (done) done();
};
},
string: function (done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected any string, got error: ' + err);
assert.strictEqual(typeof results, 'string', results + ' is not a string');
if (done) done();
};
},
positiveNumber: function (done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected positive number, got error: ' + err);
assert(results > 0, results + ' is not a positive number');
if (done) done();
};
}
},
match: function (pattern, done) {
return function (err, results) {
assert.strictEqual(err, null, 'expected ' + pattern.toString() + ', got error: ' + err);
results = arrayHelper(results);
assert(pattern.test(results), "expected string '" + results + "' to match " + pattern.toString());
if (done) done();
};
},
serverVersionAtLeast: function (connection, desired_version) {
// Wait until a connection has established (otherwise a timeout is going to be triggered at some point)
if (Object.keys(connection.server_info).length === 0) {
throw new Error('Version check not possible as the client is not yet ready or did not expose the version');
}
// Return true if the server version >= desired_version
var version = connection.server_info.versions;
for (var i = 0; i < 3; i++) {
if (version[i] > desired_version[i]) {
return true;
}
if (version[i] < desired_version[i]) {
if (this.skip) this.skip();
return false;
}
}
return true;
},
allTests: function (opts, cb) {
if (!cb) {
cb = opts;
opts = {};
}
var protocols = ['IPv4'];
if (process.platform !== 'win32') {
protocols.push('IPv6', '/tmp/redis.sock');
}
var options = [{
detect_buffers: true
}, {
detect_buffers: false
}];
options.forEach(function (options) {
var strOptions = '';
var key;
for (key in options) {
if (options.hasOwnProperty(key)) {
strOptions += key + ': ' + options[key] + '; ';
}
}
describe('using options: ' + strOptions, function () {
protocols.forEach(function (ip, i) {
if (i !== 0 && !opts.allConnections) {
return;
}
cb(ip, config.configureClient(ip, options));
});
});
});
},
removeMochaListener: function () {
var mochaListener = process.listeners('uncaughtException').pop();
process.removeListener('uncaughtException', mochaListener);
return mochaListener;
},
callFuncAfter: function (func, max) {
var i = 0;
return function (err) {
if (err) {
throw err;
}
i++;
if (i >= max) {
func();
return true;
}
return false;
};
},
killConnection: function (client) {
// Change the connection option to a non existing one and destroy the stream
client.connection_options = {
port: 65535,
host: '127.0.0.1',
family: 4
};
client.address = '127.0.0.1:65535';
client.stream.destroy();
}
};
node-redis-3.0.2/test/lib/ 0000775 0000000 0000000 00000000000 13620024551 0015270 5 ustar 00root root 0000000 0000000 node-redis-3.0.2/test/lib/config.js 0000664 0000000 0000000 00000001556 13620024551 0017102 0 ustar 00root root 0000000 0000000 'use strict';
// helpers for configuring a redis client in
// its various modes, ipV6, ipV4, socket.
var redis = require('../../index');
var bluebird = require('bluebird');
// Promisify everything
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);
var config = {
redis: redis,
PORT: 6379,
HOST: {
IPv4: '127.0.0.1',
IPv6: '::1'
},
configureClient: function (ip, opts) {
var args = [];
// Do not manipulate the opts => copy them each time
opts = opts ? JSON.parse(JSON.stringify(opts)) : {};
if (ip.match(/\.sock/)) {
args.push(ip);
} else {
args.push(config.PORT);
args.push(config.HOST[ip]);
opts.family = ip;
}
args.push(opts);
return args;
}
};
module.exports = config;
node-redis-3.0.2/test/lib/good-traces.js 0000664 0000000 0000000 00000001106 13620024551 0020033 0 ustar 00root root 0000000 0000000 // Spawned by the good_stacks.spec.js tests
'use strict';
var assert = require('assert');
var redis = require('../../index');
var client = redis.createClient();
// Both error cases would normally return bad stack traces
client.set('foo', function (err, res) {
assert(/good-traces.js:9:8/.test(err.stack));
client.set('foo', 'bar', function (err, res) {
assert(/good-traces.js:11:12/.test(err.stack));
client.quit(function () {
process.exit(0);
});
});
process.nextTick(function () {
client.stream.destroy();
});
});
node-redis-3.0.2/test/lib/redis-process.js 0000664 0000000 0000000 00000006313 13620024551 0020413 0 ustar 00root root 0000000 0000000 'use strict';
// helper to start and stop the redis process.
var config = require('./config');
var fs = require('fs');
var path = require('path');
var spawn = require('cross-spawn');
var tcpPortUsed = require('tcp-port-used');
var bluebird = require('bluebird');
// wait for redis to be listening in
// all three modes (ipv4, ipv6, socket).
function waitForRedis (available, cb, port) {
if (process.platform === 'win32') return cb();
var time = Date.now();
var running = false;
var socket = '/tmp/redis.sock';
if (port) {
// We have to distinguishe the redis sockets if we have more than a single redis instance running
socket = '/tmp/redis' + port + '.sock';
}
port = port || config.PORT;
var id = setInterval(function () {
if (running) return;
running = true;
bluebird.join(
tcpPortUsed.check(port, '127.0.0.1'),
tcpPortUsed.check(port, '::1'),
function (ipV4, ipV6) {
if (ipV6 === available && ipV4 === available) {
if (fs.existsSync(socket) === available) {
clearInterval(id);
return cb();
}
// The same message applies for can't stop but we ignore that case
throw new Error('Port ' + port + ' is already in use. Tests can\'t start.\n');
}
if (Date.now() - time > 6000) {
throw new Error('Redis could not start on port ' + (port || config.PORT) + '\n');
}
running = false;
}
).catch(function (err) {
console.error('\x1b[31m' + err.stack + '\x1b[0m\n');
process.exit(1);
});
}, 100);
}
module.exports = {
start: function (done, conf, port) {
var spawnFailed = false;
// spawn redis with our testing configuration.
var confFile = conf || path.resolve(__dirname, '../conf/redis.conf');
var rp = spawn('redis-server', [confFile], {});
// capture a failure booting redis, and give
// the user running the test some directions.
rp.once('exit', function (code) {
if (code !== 0) spawnFailed = true;
});
// wait for redis to become available, by
// checking the port we bind on.
waitForRedis(true, function () {
// return an object that can be used in
// an after() block to shutdown redis.
return done(null, {
spawnFailed: function () {
return spawnFailed;
},
stop: function (done) {
if (spawnFailed) return done();
rp.once('exit', function (code) {
var error = null;
if (code !== null && code !== 0) {
error = new Error('Redis shutdown failed with code ' + code);
}
waitForRedis(false, function () {
return done(error);
}, port);
});
rp.kill('SIGTERM');
}
});
}, port);
}
};
node-redis-3.0.2/test/lib/stunnel-process.js 0000664 0000000 0000000 00000005006 13620024551 0020773 0 ustar 00root root 0000000 0000000 'use strict';
// helper to start and stop the stunnel process.
var spawn = require('child_process').spawn;
var EventEmitter = require('events');
var fs = require('fs');
var path = require('path');
var util = require('util');
// Newer Node.js versions > 0.10 return the EventEmitter right away and using .EventEmitter was deprecated
if (typeof EventEmitter !== 'function') {
EventEmitter = EventEmitter.EventEmitter;
}
function once (cb) {
var called = false;
return function () {
if (called) return;
called = true;
cb.apply(this, arguments);
};
}
function StunnelProcess (conf_dir) {
EventEmitter.call(this);
// set up an stunnel to redis; edit the conf file to include required absolute paths
var conf_file = path.resolve(conf_dir, 'stunnel.conf');
var conf_text = fs.readFileSync(conf_file + '.template').toString().replace(/__dirname/g, conf_dir);
fs.writeFileSync(conf_file, conf_text);
var stunnel = this.stunnel = spawn('stunnel', [conf_file]);
// handle child process events, and failure to set up tunnel
var self = this;
this.timer = setTimeout(function () {
self.emit('error', new Error('Timeout waiting for stunnel to start'));
}, 8000);
stunnel.on('error', function (err) {
self.clear();
self.emit('error', err);
});
stunnel.on('exit', function (code) {
self.clear();
if (code === 0) {
self.emit('stopped');
} else {
self.emit('error', new Error('Stunnel exited unexpectedly; code = ' + code));
}
});
// wait to stunnel to start
stunnel.stderr.on('data', function (data) {
if (data.toString().match(/Service.+redis.+bound/)) {
clearTimeout(this.timer);
self.emit('started');
}
});
}
util.inherits(StunnelProcess, EventEmitter);
StunnelProcess.prototype.clear = function () {
this.stunnel = null;
clearTimeout(this.timer);
};
StunnelProcess.prototype.stop = function (done) {
if (this.stunnel) {
this.stunnel.kill();
}
};
module.exports = {
start: function (done, conf_dir) {
done = once(done);
var stunnel = new StunnelProcess(conf_dir);
stunnel.once('error', done.bind(done));
stunnel.once('started', done.bind(done, null, stunnel));
},
stop: function (stunnel, done) {
stunnel.removeAllListeners();
stunnel.stop();
stunnel.once('error', done.bind(done));
stunnel.once('stopped', done.bind(done, null));
}
};
node-redis-3.0.2/test/lib/unref.js 0000664 0000000 0000000 00000001026 13620024551 0016744 0 ustar 00root root 0000000 0000000 // spawned by the unref tests in node_redis.spec.js.
// when configured, unref causes the client to exit
// as soon as there are no outstanding commands.
'use strict';
var redis = require('../../index');
var HOST = process.argv[2] || '127.0.0.1';
var PORT = process.argv[3];
var args = PORT ? [PORT, HOST] : [HOST];
var c = redis.createClient.apply(redis, args);
c.info(function (err, reply) {
if (err) process.exit(-1);
if (!reply.length) process.exit(-1);
process.stdout.write(reply.length.toString());
});
c.unref();
node-redis-3.0.2/test/multi.spec.js 0000664 0000000 0000000 00000106435 13620024551 0017154 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var utils = require('../lib/utils');
var redis = config.redis;
var zlib = require('zlib');
var client;
describe("The 'multi' method", function () {
afterEach(function () {
client.end(true);
});
describe('regression test', function () {
it('saved buffers with charsets different than utf-8 (issue #913)', function (done) {
this.timeout(12000); // Windows tests on 0.10 are slow
client = redis.createClient();
var end = helper.callFuncAfter(done, 100);
// Some random object created from http://beta.json-generator.com/
var test_obj = {
'_id': '5642c4c33d4667c4a1fefd99', 'index': 0, 'guid': '5baf1f1c-7621-41e7-ae7a-f8c6f3199b0f', 'isActive': true,
'balance': '$1,028.63', 'picture': 'http://placehold.it/32x32', 'age': 31, 'eyeColor': 'green', 'name': {'first': 'Shana', 'last': 'Long'},
'company': 'MANGLO', 'email': 'shana.long@manglo.us', 'phone': '+1 (926) 405-3105', 'address': '747 Dank Court, Norfolk, Ohio, 1112',
'about': 'Eu pariatur in nisi occaecat enim qui consequat nostrud cupidatat id. ' +
'Commodo commodo dolore esse irure minim quis deserunt anim laborum aute deserunt et est. Quis nisi laborum deserunt nisi quis.',
'registered': 'Friday, April 18, 2014 9:56 AM', 'latitude': '74.566613', 'longitude': '-11.660432', 'tags': [7, 'excepteur'],
'range': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'friends': [3, {'id': 1, 'name': 'Schultz Dyer'}],
'greeting': 'Hello, Shana! You have 5 unread messages.', 'favoriteFruit': 'strawberry'
};
function run () {
if (end() === true) {
return;
}
// To demonstrate a big payload for hash set field values, let's create a big array
var test_arr = [];
var i = 0;
for (; i < 80; i++) {
var new_obj = JSON.parse(JSON.stringify(test_obj));
test_arr.push(new_obj);
}
var json = JSON.stringify(test_arr);
zlib.deflate(new Buffer(json), function (err, buffer) {
if (err) {
done(err);
return;
}
var multi = client.multi();
multi.del('SOME_KEY');
for (i = 0; i < 100; i++) {
multi.hset('SOME_KEY', 'SOME_FIELD' + i, buffer);
}
multi.exec(function (err, res) {
if (err) {
done(err);
return;
}
run();
});
});
}
run();
});
});
describe('pipeline limit', function () {
it('do not exceed maximum string size', function (done) {
this.timeout(process.platform !== 'win32' ? 10000 : 35000); // Windows tests are horribly slow
// Triggers a RangeError: Invalid string length if not handled properly
client = redis.createClient();
var multi = client.multi();
var i = Math.pow(2, 28);
while (i > 0) {
i -= 10230;
multi.set('foo' + i, 'bar' + new Array(1024).join('1234567890'));
}
client.on('ready', function () {
multi.exec(function (err, res) {
assert.strictEqual(res.length, 26241);
});
client.flushdb(done);
});
});
});
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
describe('when not connected', function () {
beforeEach(function (done) {
var end = helper.callFuncAfter(done, 2);
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.quit(end);
});
client.once('end', end);
});
it('reports an error', function (done) {
var multi = client.multi();
var notBuffering = multi.exec(function (err, res) {
assert(err.message.match(/The connection is already closed/));
done();
});
assert.strictEqual(notBuffering, false);
});
it('reports an error if promisified', function () {
return client.multi().execAsync().catch(function (err) {
assert(err.message.match(/The connection is already closed/));
});
});
});
describe('when connected', function () {
beforeEach(function () {
client = redis.createClient.apply(null, args);
});
describe('monitor and transactions do not work together', function () {
it('results in a execabort', function (done) {
// Check that transactions in combination with monitor result in an error
client.monitor(function (e) {
client.on('error', function (err) {
assert.strictEqual(err.code, 'EXECABORT');
client.end(false);
done();
});
var multi = client.multi();
multi.set('hello', 'world');
multi.exec();
});
});
it('results in a execabort #2', function (done) {
// Check that using monitor with a transactions results in an error
client.multi().set('foo', 'bar').monitor().exec(function (err, res) {
assert.strictEqual(err.code, 'EXECABORT');
client.end(false);
done();
});
});
it('sanity check', function (done) {
// Remove the listener and add it back again after the error
var mochaListener = helper.removeMochaListener();
process.on('uncaughtException', function (err) {
helper.removeMochaListener();
process.on('uncaughtException', mochaListener);
done();
});
// Check if Redis still has the error
client.monitor();
client.send_command('multi');
client.send_command('set', ['foo', 'bar']);
client.send_command('get', ['foo']);
client.send_command('exec', function (err, res) {
// res[0] is going to be the monitor result of set
// res[1] is going to be the result of the set command
assert(utils.monitor_regex.test(res[0]));
assert.strictEqual(res[1], 'OK');
assert.strictEqual(res.length, 2);
client.end(false);
});
});
});
it('executes a pipelined multi properly in combination with the offline queue', function (done) {
var multi1 = client.multi();
multi1.set('m1', '123');
multi1.get('m1');
multi1.exec(done);
assert.strictEqual(client.offline_queue.length, 4);
});
it('executes a pipelined multi properly after a reconnect in combination with the offline queue', function (done) {
client.once('ready', function () {
client.stream.destroy();
var called = false;
var multi1 = client.multi();
multi1.set('m1', '123');
multi1.get('m1');
multi1.exec(function (err, res) {
assert(!err);
called = true;
});
client.once('ready', function () {
var multi1 = client.multi();
multi1.set('m2', '456');
multi1.get('m2');
multi1.exec(function (err, res) {
assert(called);
assert(!err);
assert.strictEqual(res[1], '456');
done();
});
});
});
});
});
describe('when connection is broken', function () {
it('return an error even if connection is in broken mode if callback is present', function (done) {
client = redis.createClient({
host: 'somewhere',
port: 6379,
retry_strategy: function (options) {
if (options.attempt > 1) {
// End reconnecting with built in error
return undefined;
}
}
});
client.on('error', function (err) {
if (/Redis connection to somewhere:6379 failed/.test(err.origin.message)) {
done();
}
});
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
assert(/Redis connection in broken state: retry aborted/.test(err.message));
assert.strictEqual(err.errors.length, 2);
assert.strictEqual(err.errors[0].args.length, 2);
});
});
it('does not emit an error twice if connection is in broken mode with no callback', function (done) {
client = redis.createClient({
host: 'somewhere',
port: 6379,
retry_strategy: function (options) {
if (options.attempt > 1) {
// End reconnecting with built in error
return undefined;
}
}
});
client.on('error', function (err) {
// Results in multiple done calls if test fails
if (/Redis connection to somewhere:6379 failed/.test(err.origin.message)) {
done();
}
});
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec();
});
});
describe('when ready', function () {
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.flushdb(function (err) {
return done(err);
});
});
});
it('returns an empty result array', function (done) {
var multi = client.multi();
var notBuffering = multi.exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.length, 0);
done();
});
assert.strictEqual(notBuffering, true);
});
it('runs normal calls in-between multis', function (done) {
var multi1 = client.multi();
multi1.set('m1', '123');
client.set('m2', '456', done);
});
it('runs simultaneous multis with the same client', function (done) {
var end = helper.callFuncAfter(done, 2);
var multi1 = client.multi();
multi1.set('m1', '123');
multi1.get('m1');
var multi2 = client.multi();
multi2.set('m2', '456');
multi2.get('m2');
multi1.exec(end);
multi2.exec(function (err, res) {
assert.strictEqual(res[1], '456');
end();
});
});
it('runs simultaneous multis with the same client version 2', function (done) {
var end = helper.callFuncAfter(done, 2);
var multi2 = client.multi();
var multi1 = client.multi();
multi2.set('m2', '456');
multi1.set('m1', '123');
multi1.get('m1');
multi2.get('m1');
multi2.ping();
multi1.exec(end);
multi2.exec(function (err, res) {
assert.strictEqual(res[1], '123');
end();
});
});
it('roles back a transaction when one command in a sequence of commands fails', function (done) {
var multi1, multi2;
// Provoke an error at queue time
multi1 = client.MULTI();
multi1.mset('multifoo', '10', 'multibar', '20', helper.isString('OK'));
multi1.set('foo2', helper.isError());
multi1.incr('multifoo');
multi1.incr('multibar');
multi1.exec(function () {
// Redis 2.6.5+ will abort transactions with errors
// see: http://redis.io/topics/transactions
var multibar_expected = 1;
var multifoo_expected = 1;
// Confirm that the previous command, while containing an error, still worked.
multi2 = client.multi();
multi2.incr('multibar', helper.isNumber(multibar_expected));
multi2.incr('multifoo', helper.isNumber(multifoo_expected));
multi2.exec(function (err, replies) {
assert.strictEqual(multibar_expected, replies[0]);
assert.strictEqual(multifoo_expected, replies[1]);
return done();
});
});
});
it('roles back a transaction when one command in an array of commands fails', function (done) {
// test nested multi-bulk replies
client.multi([
['mget', 'multifoo', 'multibar', function (err, res) {
assert.strictEqual(2, res.length);
assert.strictEqual(0, +res[0]);
assert.strictEqual(0, +res[1]);
}],
['set', 'foo2', helper.isError()],
['incr', 'multifoo'],
['incr', 'multibar']
]).exec(function (err, replies) {
assert.notEqual(err, null);
assert.equal(replies, undefined);
return done();
});
});
it('handles multiple operations being applied to a set', function (done) {
client.sadd('some set', 'mem 1');
client.sadd(['some set', 'mem 2']);
client.sadd('some set', 'mem 3');
client.sadd('some set', 'mem 4');
// make sure empty mb reply works
client.del('some missing set');
client.smembers('some missing set', function (err, reply) {
// make sure empty mb reply works
assert.strictEqual(0, reply.length);
});
// test nested multi-bulk replies with empty mb elements.
client.multi([
['smembers', ['some set']],
['del', 'some set'],
['smembers', 'some set']
])
.scard('some set')
.exec(function (err, replies) {
assert.strictEqual(4, replies[0].length);
assert.strictEqual(0, replies[2].length);
return done();
});
});
it('allows multiple operations to be performed using constructor with all kinds of syntax', function (done) {
var now = Date.now();
var arr = ['multihmset', 'multibar', 'multibaz'];
var arr2 = ['some manner of key', 'otherTypes'];
var arr3 = [5768, 'multibarx', 'multifoox'];
var arr4 = ['mset', [578, 'multibar'], helper.isString('OK')];
var called = false;
client.multi([
arr4,
[['mset', 'multifoo2', 'multibar2', 'multifoo3', 'multibar3'], helper.isString('OK')],
['hmset', arr],
[['hmset', 'multihmset2', 'multibar2', 'multifoo3', 'multibar3', 'test'], helper.isString('OK')],
['hmset', ['multihmset', 'multibar', 'multifoo'], helper.isString('OK')],
['hmset', arr3, helper.isString('OK')],
['hmset', now, {123456789: 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}],
['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}, helper.isString('OK')],
['HMSET', 'multihmset', ['multibar', 'multibaz'], undefined], // undefined is used as a explicit not set callback variable
['hmset', 'multihmset', ['multibar', 'multibaz'], helper.isString('OK')],
])
.hmget(now, 123456789, 'otherTypes')
.hmget('key2', arr2, function noop () {})
.hmget(['multihmset2', 'some manner of key', 'multibar3'])
.mget('multifoo2', ['multifoo3', 'multifoo'], function (err, res) {
assert(res[0], 'multifoo3');
assert(res[1], 'multifoo');
called = true;
})
.exec(function (err, replies) {
assert(called);
assert.equal(arr.length, 3);
assert.equal(arr2.length, 2);
assert.equal(arr3.length, 3);
assert.equal(arr4.length, 3);
assert.strictEqual(null, err);
assert.equal(replies[10][1], '555');
assert.equal(replies[11][0], 'a type of value');
assert.strictEqual(replies[12][0], null);
assert.equal(replies[12][1], 'test');
assert.equal(replies[13][0], 'multibar2');
assert.equal(replies[13].length, 3);
assert.equal(replies.length, 14);
return done();
});
});
it('converts a non string key to a string', function (done) {
// TODO: Converting the key might change soon again.
client.multi().hmset(true, {
test: 123,
bar: 'baz'
}).exec(done);
});
it('runs a multi without any further commands', function (done) {
var buffering = client.multi().exec(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.length, 0);
done();
});
assert(typeof buffering === 'boolean');
});
it('allows multiple operations to be performed using a chaining API', function (done) {
client.multi()
.mset('some', '10', 'keys', '20')
.incr('some')
.incr('keys')
.mget('some', ['keys'])
.exec(function (err, replies) {
assert.strictEqual(null, err);
assert.equal('OK', replies[0]);
assert.equal(11, replies[1]);
assert.equal(21, replies[2]);
assert.equal(11, replies[3][0].toString());
assert.equal(21, replies[3][1].toString());
return done();
});
});
it('allows multiple commands to work the same as normal to be performed using a chaining API', function (done) {
client.multi()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.exec(function (err, replies) {
assert.strictEqual(null, err);
assert.equal('OK', replies[0]);
assert.equal(11, replies[1]);
assert.equal(21, replies[2]);
assert.equal(11, replies[3][0].toString());
assert.equal(21, replies[3][1].toString());
return done();
});
});
it('allows multiple commands to work the same as normal to be performed using a chaining API promisified', function () {
return client.multi()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.execAsync()
.then(function (replies) {
assert.equal('OK', replies[0]);
assert.equal(11, replies[1]);
assert.equal(21, replies[2]);
assert.equal(11, replies[3][0].toString());
assert.equal(21, replies[3][1].toString());
});
});
it('allows an array to be provided indicating multiple operations to perform', function (done) {
// test nested multi-bulk replies with nulls.
client.multi([
['mget', ['multifoo', 'some', 'random value', 'keys']],
['incr', 'multifoo']
])
.exec(function (err, replies) {
assert.strictEqual(replies.length, 2);
assert.strictEqual(replies[0].length, 4);
return done();
});
});
it('allows multiple operations to be performed on a hash', function (done) {
client.multi()
.hmset('multihash', 'a', 'foo', 'b', 1)
.hmset('multihash', {
extra: 'fancy',
things: 'here'
})
.hgetall('multihash')
.exec(function (err, replies) {
assert.strictEqual(null, err);
assert.equal('OK', replies[0]);
assert.equal(Object.keys(replies[2]).length, 4);
assert.equal('foo', replies[2].a);
assert.equal('1', replies[2].b);
assert.equal('fancy', replies[2].extra);
assert.equal('here', replies[2].things);
return done();
});
});
it('reports EXECABORT exceptions when they occur (while queueing)', function (done) {
client.multi().config('bar').set('foo').set('bar').exec(function (err, reply) {
assert.equal(err.code, 'EXECABORT');
assert.equal(reply, undefined, 'The reply should have been discarded');
assert(err.message.match(/^EXECABORT/), 'Error message should begin with EXECABORT');
assert.equal(err.errors.length, 2, 'err.errors should have 2 items');
assert.strictEqual(err.errors[0].command, 'SET');
assert.strictEqual(err.errors[0].code, 'ERR');
assert.strictEqual(err.errors[0].position, 1);
assert(/^ERR/.test(err.errors[0].message), 'Actuall error message should begin with ERR');
return done();
});
});
it('reports multiple exceptions when they occur (while EXEC is running)', function (done) {
client.multi().config('bar').debug('foo').eval("return {err='this is an error'}", 0).exec(function (err, reply) {
assert.strictEqual(reply.length, 3);
assert.equal(reply[0].code, 'ERR');
assert.equal(reply[0].command, 'CONFIG');
assert.equal(reply[2].code, undefined);
assert.equal(reply[2].command, 'EVAL');
assert(/^this is an error/.test(reply[2].message));
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR');
assert(/^ERR/.test(reply[1].message), 'Error message should begin with ERR');
return done();
});
});
it('reports multiple exceptions when they occur (while EXEC is running) promisified', function () {
return client.multi().config('bar').debug('foo').eval("return {err='this is an error'}", 0).execAsync().then(function (reply) {
assert.strictEqual(reply.length, 3);
assert.equal(reply[0].code, 'ERR');
assert.equal(reply[0].command, 'CONFIG');
assert.equal(reply[2].code, undefined);
assert.equal(reply[2].command, 'EVAL');
assert(/^this is an error/.test(reply[2].message));
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR');
assert(/^ERR/.test(reply[1].message), 'Error message should begin with ERR');
});
});
it('reports multiple exceptions when they occur (while EXEC is running) and calls cb', function (done) {
var multi = client.multi();
multi.config('bar', helper.isError());
multi.set('foo', 'bar', helper.isString('OK'));
multi.debug('foo').exec(function (err, reply) {
assert.strictEqual(reply.length, 3);
assert.strictEqual(reply[0].code, 'ERR');
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR');
assert(/^ERR/.test(reply[2].message), 'Error message should begin with ERR');
assert.strictEqual(reply[1], 'OK');
client.get('foo', helper.isString('bar', done));
});
});
it('emits an error if no callback has been provided and execabort error occured', function (done) {
var multi = client.multi();
multi.config('bar');
multi.set('foo');
multi.exec();
client.on('error', function (err) {
assert.equal(err.code, 'EXECABORT');
done();
});
});
it('should work without any callback', function (done) {
var multi = client.multi();
multi.set('baz', 'binary');
multi.set('foo', 'bar');
multi.exec();
client.get('foo', helper.isString('bar', done));
});
it('should not use a transaction with exec_atomic if no command is used', function () {
var multi = client.multi();
var test = false;
multi.exec_batch = function () {
test = true;
};
multi.exec_atomic();
assert(test);
});
it('should not use a transaction with exec_atomic if only one command is used', function () {
var multi = client.multi();
var test = false;
multi.exec_batch = function () {
test = true;
};
multi.set('baz', 'binary');
multi.EXEC_ATOMIC();
assert(test);
});
it('should use transaction with exec_atomic and more than one command used', function (done) {
var multi = client.multi();
var test = false;
multi.exec_batch = function () {
test = true;
};
multi.set('baz', 'binary');
multi.get('baz');
multi.exec_atomic(done);
assert(!test);
});
it('do not mutate arguments in the multi constructor', function (done) {
var input = [['set', 'foo', 'bar'], ['get', 'foo']];
client.multi(input).exec(function (err, res) {
assert.strictEqual(input.length, 2);
assert.strictEqual(input[0].length, 3);
assert.strictEqual(input[1].length, 2);
done();
});
});
it('works properly after a reconnect. issue #897', function (done) {
client.stream.destroy();
client.on('error', function (err) {
assert.strictEqual(err.code, 'ECONNREFUSED');
});
client.on('ready', function () {
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
assert(!err);
assert.strictEqual(res[1], 'bar');
done();
});
});
});
it('emits error once if reconnecting after multi has been executed but not yet returned without callback', function (done) {
// NOTE: If uncork is called async by postponing it to the next tick, this behavior is going to change.
// The command won't be processed anymore two errors are returned instead of one
client.on('error', function (err) {
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
client.get('foo', function (err, res) {
assert.strictEqual(res, 'bar');
done();
});
});
// The commands should still be fired, no matter that the socket is destroyed on the same tick
client.multi().set('foo', 'bar').get('foo').exec();
// Abort connection before the value returned
client.stream.destroy();
});
it('indivdual commands work properly with multi', function (done) {
// Neither of the following work properly in a transactions:
// (This is due to Redis not returning the reply as expected / resulting in undefined behavior)
// (Likely there are more commands that do not work with a transaction)
//
// auth => can't be called after a multi command
// monitor => results in faulty return values e.g. multi().monitor().set('foo', 'bar').get('foo')
// returns ['OK, 'OK', 'monitor reply'] instead of ['OK', 'OK', 'bar']
// quit => ends the connection before the exec
// client reply skip|off => results in weird return values. Not sure what exactly happens
// subscribe => enters subscribe mode and this does not work in combination with exec (the same for psubscribe, unsubscribe...)
//
// Make sure send_command is not called
client.send_command = function () {
throw new Error('failed');
};
assert.strictEqual(client.selected_db, undefined);
var multi = client.multi();
multi.select(5, function (err, res) {
assert.strictEqual(client.selected_db, 5);
assert.strictEqual(res, 'OK');
assert.notDeepEqual(client.server_info.db5, { avg_ttl: 0, expires: 0, keys: 1 });
});
// multi.client('reply', 'on', helper.isString('OK')); // Redis v.3.2
multi.set('foo', 'bar', helper.isString('OK'));
multi.info(function (err, res) {
assert.strictEqual(res.indexOf('# Server\r\nredis_version:'), 0);
assert.deepEqual(client.server_info.db5, { avg_ttl: 0, expires: 0, keys: 1 });
});
multi.get('foo', helper.isString('bar'));
multi.exec(function (err, res) {
res[2] = res[2].substr(0, 10);
assert.deepEqual(res, ['OK', 'OK', '# Server\r\n', 'bar']);
client.flushdb(done);
});
});
});
});
});
});
node-redis-3.0.2/test/node_redis.spec.js 0000664 0000000 0000000 00000151632 13620024551 0020134 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var fs = require('fs');
var util = require('util');
var path = require('path');
var intercept = require('intercept-stdout');
var config = require('./lib/config');
var helper = require('./helper');
var fork = require('child_process').fork;
var redis = config.redis;
var client;
// Currently Travis Windows builds hang after completing if any processes are still running,
// we shutdown redis-server after all tests complete (can't do this in a
// `after_script` Travis hook as it hangs before the `after` life cycles)
// to workaround the issue.
//
// See: https://github.com/travis-ci/travis-ci/issues/8082
after(function (done) {
if (process.platform !== 'win32' || !process.env.CI) {
return done();
}
process.nextTick(function () {
require('cross-spawn').sync('redis-server', ['--service-stop'], {});
done();
});
});
describe('The node_redis client', function () {
describe("The 'add_command' method", function () {
var Redis = redis.RedisClient;
it('camel case and snakeCase version exists', function () {
assert.strictEqual(typeof redis.addCommand, 'function');
assert.strictEqual(typeof redis.add_command, 'function');
});
it('converts special characters in functions names to lowercase', function () {
var command = 'really-new.command';
assert.strictEqual(Redis.prototype[command], undefined);
redis.addCommand(command);
if (Redis.prototype[command].name !== '') {
assert.strictEqual(Redis.prototype[command].name, 'really_new_command');
assert.strictEqual(Redis.prototype[command.toUpperCase()].name, 'really_new_command');
assert.strictEqual(Redis.prototype.really_new_command.name, 'really_new_command');
assert.strictEqual(Redis.prototype.REALLY_NEW_COMMAND.name, 'really_new_command');
}
});
});
it('individual commands sanity check', function (done) {
// All commands should work the same in multi context or without
// Therefor individual commands always have to be handled in both cases
fs.readFile(path.resolve(__dirname, '../lib/individualCommands.js'), 'utf8', function (err, data) {
var client_prototype = data.match(/(\n| = )RedisClient\.prototype.[a-zA-Z_]+/g);
var multi_prototype = data.match(/(\n| = )Multi\.prototype\.[a-zA-Z_]+/g);
// Check that every entry RedisClient entry has a correspondend Multi entry
assert.strictEqual(client_prototype.filter(function (entry) {
return multi_prototype.indexOf(entry.replace('RedisClient', 'Multi')) === -1;
}).length, 4); // multi and batch are included too
assert.strictEqual(client_prototype.length, multi_prototype.length + 4);
// Check that all entries exist in uppercase and in lowercase variants
assert.strictEqual(data.match(/(\n| = )RedisClient\.prototype.[a-z_]+/g).length * 2, client_prototype.length);
done();
});
});
it('convert minus to underscore in Redis function names', function (done) {
var names = Object.keys(redis.RedisClient.prototype);
client = redis.createClient();
for (var i = 0; i < names.length; i++) {
assert(/^([a-zA-Z_][a-zA-Z_0-9]*)?$/.test(client[names[i]].name));
}
client.quit(done);
});
it('reset the parser while reconnecting (See #1190)', function (done) {
var client = redis.createClient({
retryStrategy: function () {
return 5;
}
});
client.once('reconnecting', function () {
process.nextTick(function () {
assert.strictEqual(client.reply_parser.buffer, null);
done();
});
});
var partialInput = new Buffer('$100\r\nabcdef');
client.reply_parser.execute(partialInput);
assert.strictEqual(client.reply_parser.buffer.inspect(), partialInput.inspect());
client.stream.destroy();
});
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
afterEach(function () {
client.end(true);
});
describe('when connected', function () {
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
client.once('connect', function () {
client.flushdb(done);
});
});
describe('duplicate', function () {
it('check if all options got copied properly', function (done) {
client.selected_db = 2;
var client2 = client.duplicate();
assert.strictEqual(client.connectionId + 1, client2.connection_id);
assert.strictEqual(client2.selected_db, 2);
assert(client.connected);
assert(!client2.connected);
for (var elem in client.options) {
if (client.options.hasOwnProperty(elem)) {
assert.strictEqual(client2.options[elem], client.options[elem]);
}
}
client2.on('error', function (err) {
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted. It might have been processed.');
assert.strictEqual(err.command, 'SELECT');
assert(err instanceof Error);
assert.strictEqual(err.name, 'AbortError');
});
client2.on('ready', function () {
client2.end(true);
done();
});
});
it('check if all new options replaced the old ones', function (done) {
client.selected_db = 1;
var client2 = client.duplicate({
db: 2,
no_ready_check: true
});
assert(client.connected);
assert(!client2.connected);
assert.notEqual(client.selected_db, client2.selected_db);
assert.strictEqual(client.options.no_ready_check, undefined);
assert.strictEqual(client2.options.no_ready_check, true);
assert.notDeepEqual(client.options, client2.options);
for (var elem in client.options) {
if (client.options.hasOwnProperty(elem)) {
if (elem !== 'no_ready_check') {
assert.strictEqual(client2.options[elem], client.options[elem]);
}
}
}
client2.on('ready', function () {
client2.end(true);
done();
});
});
it('works with a callback', function (done) {
client.duplicate(function (err, client) {
assert(!err);
assert.strictEqual(client.ready, true);
client.quit(done);
});
});
it('works with a callback and errors out', function (done) {
client.duplicate({
port: '9999'
}, function (err, client) {
assert.strictEqual(err.code, 'ECONNREFUSED');
done(client);
});
});
it('works with a promises', function () {
return client.duplicateAsync().then(function (client) {
assert.strictEqual(client.ready, true);
return client.quitAsync();
});
});
it('works with a promises and errors', function () {
return client.duplicateAsync({
port: 9999
}).catch(function (err) {
assert.strictEqual(err.code, 'ECONNREFUSED');
});
});
});
describe('big data', function () {
// Check if the fast mode for big strings is working correct
it('safe strings that are bigger than 30000 characters', function (done) {
var str = 'foo ಠ_ಠ bar ';
while (str.length < 111111) {
str += str;
}
client.set('foo', str);
client.get('foo', function (err, res) {
assert.strictEqual(res, str);
done();
});
});
it('safe strings that are bigger than 30000 characters with multi', function (done) {
var str = 'foo ಠ_ಠ bar ';
while (str.length < 111111) {
str += str;
}
var called = false;
var temp = client.write_buffers.bind(client);
assert(client.fire_strings);
client.write_buffers = function (data) {
called = true;
// To increase write performance for strings the value is converted to a buffer
assert(!client.fire_strings);
temp(data);
};
client.multi().set('foo', str).get('foo', function (err, res) {
assert.strictEqual(res, str);
}).exec(function (err, res) {
assert(called);
assert.strictEqual(res[1], str);
done();
});
assert(client.fire_strings);
});
});
describe('send_command', function () {
it('omitting args should be fine', function (done) {
client.server_info = {};
client.send_command('info');
client.send_command('ping', function (err, res) {
assert.strictEqual(res, 'PONG');
// Check if the previous info command used the internal individual info command
assert.notDeepEqual(client.server_info, {});
client.server_info = {};
});
client.send_command('info', null, undefined);
client.send_command('ping', null, function (err, res) {
assert.strictEqual(res, 'PONG');
// Check if the previous info command used the internal individual info command
assert.notDeepEqual(client.server_info, {});
client.server_info = {};
});
client.send_command('info', undefined, undefined);
client.send_command('ping', function (err, res) {
assert.strictEqual(res, 'PONG');
// Check if the previous info command used the internal individual info command
assert.notDeepEqual(client.server_info, {});
client.server_info = {};
});
client.send_command('info', undefined, function (err, res) {
assert(/redis_version/.test(res));
// The individual info command should also be called by using send_command
// console.log(info, client.server_info);
assert.notDeepEqual(client.server_info, {});
done();
});
});
it('using multi with sendCommand should work as individual command instead of using the internal multi', function (done) {
// This is necessary to keep backwards compatibility and it is the only way to handle multis as you want in node_redis
client.sendCommand('multi');
client.sendCommand('set', ['foo', 'bar'], helper.isString('QUEUED'));
client.get('foo');
client.exec(function (err, res) { // exec is not manipulated if not fired by the individual multi command
// As the multi command is handled individually by the user he also has to handle the return value
assert.strictEqual(res[0].toString(), 'OK');
assert.strictEqual(res[1].toString(), 'bar');
done();
});
});
it('multi should be handled special', function (done) {
client.send_command('multi', undefined, helper.isString('OK'));
var args = ['test', 'bla'];
client.send_command('set', args, helper.isString('QUEUED'));
assert.deepEqual(args, ['test', 'bla']); // Check args manipulation
client.get('test', helper.isString('QUEUED'));
client.exec(function (err, res) {
// As the multi command is handled individually by the user he also has to handle the return value
assert.strictEqual(res[0].toString(), 'OK');
assert.strictEqual(res[1].toString(), 'bla');
done();
});
});
it('using another type as cb should throw', function () {
try {
client.send_command('set', ['test', 'bla'], [true]);
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Array" for callback function');
}
try {
client.send_command('set', ['test', 'bla'], null);
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "null" for callback function');
}
});
it('command argument has to be of type string', function () {
try {
client.send_command(true, ['test', 'bla'], function () {});
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Boolean" for command name');
}
try {
client.send_command(undefined, ['test', 'bla'], function () {});
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "undefined" for command name');
}
try {
client.send_command(null, ['test', 'bla'], function () {});
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "null" for command name');
}
});
it('args may only be of type Array or undefined', function () {
try {
client.send_command('info', 123);
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Number" for args');
}
});
it('passing a callback as args and as callback should throw', function () {
try {
client.send_command('info', function a () {}, function b () {});
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Function" for args');
}
});
it('multi should be handled special', function (done) {
client.send_command('multi', undefined, helper.isString('OK'));
var args = ['test', 'bla'];
client.send_command('set', args, helper.isString('QUEUED'));
assert.deepEqual(args, ['test', 'bla']); // Check args manipulation
client.get('test', helper.isString('QUEUED'));
client.exec(function (err, res) {
// As the multi command is handled individually by the user he also has to handle the return value
assert.strictEqual(res[0].toString(), 'OK');
assert.strictEqual(res[1].toString(), 'bla');
done();
});
});
it('the args array may contain a arbitrary number of arguments', function (done) {
client.send_command('mset', ['foo', 1, 'bar', 2, 'baz', 3], helper.isString('OK'));
client.mget(['foo', 'bar', 'baz'], function (err, res) {
// As the multi command is handled individually by the user he also has to handle the return value
assert.strictEqual(res[0].toString(), '1');
assert.strictEqual(res[1].toString(), '2');
assert.strictEqual(res[2].toString(), '3');
done();
});
});
it('send_command with callback as args', function (done) {
client.send_command('abcdef', function (err, res) {
if (process.platform === 'win32') {
assert.strictEqual(err.message, "ERR unknown command 'abcdef'");
} else {
assert.strictEqual(err.message, 'ERR unknown command `abcdef`, with args beginning with: ');
}
done();
});
});
});
describe('retry_unfulfilled_commands', function () {
it('should retry all commands instead of returning an error if a command did not yet return after a connection loss', function (done) {
var bclient = redis.createClient({
retry_unfulfilled_commands: true
});
bclient.blpop('blocking list 2', 5, function (err, value) {
assert.strictEqual(value[0], 'blocking list 2');
assert.strictEqual(value[1], 'initial value');
bclient.end(true);
done(err);
});
bclient.once('ready', function () {
setTimeout(function () {
bclient.stream.destroy();
client.rpush('blocking list 2', 'initial value', helper.isNumber(1));
}, 100);
});
});
it('should retry all commands even if the offline queue is disabled', function (done) {
var bclient = redis.createClient({
enableOfflineQueue: false,
retryUnfulfilledCommands: true
});
bclient.once('ready', function () {
bclient.blpop('blocking list 2', 5, function (err, value) {
assert.strictEqual(value[0], 'blocking list 2');
assert.strictEqual(value[1], 'initial value');
bclient.end(true);
done(err);
});
setTimeout(function () {
bclient.stream.destroy();
client.rpush('blocking list 2', 'initial value', helper.isNumber(1));
}, 100);
});
});
});
describe('.end', function () {
it('used without flush / flush set to false', function (done) {
var finished = false;
var end = helper.callFuncAfter(function () {
if (!finished) {
done(new Error('failed'));
}
}, 20);
var cb = function (err, res) {
assert(/Connection forcefully ended|The connection is already closed./.test(err.message));
assert.strictEqual(err.code, 'NR_CLOSED');
end();
};
for (var i = 0; i < 20; i++) {
if (i === 10) {
client.end();
}
client.set('foo', 'bar', cb);
}
client.on('warning', function () {}); // Ignore deprecation message
setTimeout(function () {
finished = true;
done();
}, 25);
});
it('used with flush set to true', function (done) {
var end = helper.callFuncAfter(function () {
done();
}, 20);
var cb = function (err, res) {
assert(/Connection forcefully ended|The connection is already closed./.test(err.message));
end();
};
for (var i = 0; i < 20; i++) {
if (i === 10) {
client.end(true);
client.stream.write('foo'); // Trigger an error on the closed stream that we ignore
}
client.set('foo', 'bar', cb);
}
});
it('emits an aggregate error if no callback was present for multiple commands in debug_mode', function (done) {
redis.debug_mode = true;
var unhookIntercept = intercept(function (data) {
return ''; // Don't print the debug messages
});
client.set('foo', 'bar');
client.set('baz', 'hello world');
client.on('error', function (err) {
assert(err instanceof Error);
assert(err instanceof redis.AbortError);
assert(err instanceof redis.AggregateError);
assert.strictEqual(err.name, 'AggregateError');
assert.strictEqual(err.errors.length, 2);
assert.strictEqual(err.message, 'Connection forcefully ended and commands aborted.');
assert.strictEqual(err.code, 'NR_CLOSED');
assert.strictEqual(err.errors[0].message, 'Connection forcefully ended and command aborted. It might have been processed.');
assert.strictEqual(err.errors[0].command, 'SET');
assert.strictEqual(err.errors[0].code, 'NR_CLOSED');
assert.deepEqual(err.errors[0].args, ['foo', 'bar']);
done();
});
client.end(true);
unhookIntercept();
redis.debug_mode = false;
});
it('emits an abort error if no callback was present for a single commands', function (done) {
redis.debug_mode = true;
var unhookIntercept = intercept(function (data) {
return ''; // Don't print the debug messages
});
client.set('foo', 'bar');
client.on('error', function (err) {
assert(err instanceof Error);
assert(err instanceof redis.AbortError);
assert(!(err instanceof redis.AggregateError));
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted. It might have been processed.');
assert.strictEqual(err.command, 'SET');
assert.strictEqual(err.code, 'NR_CLOSED');
assert.deepEqual(err.args, ['foo', 'bar']);
done();
});
client.end(true);
unhookIntercept();
redis.debug_mode = false;
});
it('does not emit abort errors if no callback was present while not being in debug_mode ', function (done) {
client.set('foo', 'bar');
client.end(true);
setTimeout(done, 100);
});
});
describe('commands after using .quit should fail', function () {
it('return an error in the callback', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
// TODO: Investigate why this test is failing hard and killing mocha if using '/tmp/redis.sock'.
// Seems like something is wrong with nyc while passing a socket connection to create client!
client = redis.createClient();
client.quit(function () {
client.get('foo', function (err, res) {
assert.strictEqual(err.message, 'Stream connection ended and command aborted. It might have been processed.');
assert.strictEqual(client.offline_queue.length, 0);
done();
});
});
});
it('return an error in the callback version two', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.quit();
setTimeout(function () {
client.get('foo', function (err, res) {
assert.strictEqual(err.message, 'GET can\'t be processed. The connection is already closed.');
assert.strictEqual(err.command, 'GET');
assert.strictEqual(client.offline_queue.length, 0);
done();
});
}, 50);
});
it('emit an error', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.quit();
client.on('error', function (err) {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is already closed.');
assert.strictEqual(err.command, 'SET');
assert.strictEqual(client.offline_queue_length, 0);
done();
});
setTimeout(function () {
client.set('foo', 'bar');
}, 50);
});
});
describe('when redis closes unexpectedly', function () {
it('reconnects and can retrieve the pre-existing data', function (done) {
client.on('reconnecting', function on_recon (params) {
client.on('connect', function on_connect () {
var end = helper.callFuncAfter(function () {
client.removeListener('connect', on_connect);
client.removeListener('reconnecting', on_recon);
assert.strictEqual(client.server_info.db0.keys, 2);
assert.strictEqual(Object.keys(client.server_info.db0).length, 3);
done();
}, 4);
client.get('recon 1', helper.isString('one', end));
client.get('recon 1', helper.isString('one', end));
client.get('recon 2', helper.isString('two', end));
client.get('recon 2', helper.isString('two', end));
});
});
client.set('recon 1', 'one');
client.set('recon 2', 'two', function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
it('reconnects properly when monitoring', function (done) {
client.on('reconnecting', function on_recon (params) {
client.on('ready', function on_ready () {
assert.strictEqual(client.monitoring, true, 'monitoring after reconnect');
client.removeListener('ready', on_ready);
client.removeListener('reconnecting', on_recon);
done();
});
});
assert.strictEqual(client.monitoring, false, 'monitoring off at start');
client.set('recon 1', 'one');
client.monitor(function (err, res) {
assert.strictEqual(client.monitoring, true, 'monitoring on after monitor()');
client.set('recon 2', 'two', function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
});
describe("and it's subscribed to a channel", function () {
// "Connection in subscriber mode, only subscriber commands may be used"
it('reconnects, unsubscribes, and can retrieve the pre-existing data', function (done) {
client.on('ready', function on_connect () {
client.unsubscribe(helper.isNotError());
client.on('unsubscribe', function (channel, count) {
// we should now be out of subscriber mode.
assert.strictEqual(channel, 'recon channel');
assert.strictEqual(count, 0);
client.set('foo', 'bar', helper.isString('OK', done));
});
});
client.set('recon 1', 'one');
client.subscribe('recon channel', function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
it('reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel', function (done) {
client.on('ready', function on_connect () {
client.unsubscribe('recon channel', helper.isNotError());
client.on('unsubscribe', function (channel, count) {
// we should now be out of subscriber mode.
assert.strictEqual(channel, 'recon channel');
assert.strictEqual(count, 0);
client.set('foo', 'bar', helper.isString('OK', done));
});
});
client.set('recon 1', 'one');
client.subscribe('recon channel', function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
});
describe('domain', function () {
it('allows client to be executed from within domain', function (done) {
var domain = require('domain').create();
domain.run(function () {
client.set('domain', 'value', function (err, res) {
assert.ok(process.domain);
throw new Error('ohhhh noooo');
});
});
// this is the expected and desired behavior
domain.on('error', function (err) {
assert.strictEqual(err.message, 'ohhhh noooo');
domain.exit();
done();
});
});
it('keeps the same domain by using the offline queue', function (done) {
client.end(true);
client = redis.createClient();
var testDomain = require('domain').create();
testDomain.run(function () {
client.set('FOOBAR', 'def', function () {
assert.strictEqual(process.domain, testDomain);
done();
});
});
});
it('catches all errors from within the domain', function (done) {
var domain = require('domain').create();
domain.run(function () {
if (process.versions.node.split('.')[0] >= 13) {
// Node >= 13
// Recreate client in domain so error handlers run this domain
// Changed in: "error handler runs outside of its domain"
// https://github.com/nodejs/node/pull/26211
client = redis.createClient();
}
client.end(true);
// Trigger an error within the domain
client.set('domain', 'value');
});
domain.on('error', function (err) {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is already closed.');
domain.exit();
done();
});
});
});
});
describe('utf8', function () {
it('handles utf-8 keys', function (done) {
var utf8_sample = 'ಠ_ಠ';
client.set(['utf8test', utf8_sample], helper.isString('OK'));
client.get(['utf8test'], function (err, obj) {
assert.strictEqual(utf8_sample, obj);
done(err);
});
});
});
});
describe('unref', function () {
it('exits subprocess as soon as final command is processed', function (done) {
this.timeout(12000);
var args = config.HOST[ip] ? [config.HOST[ip], config.PORT] : [ip];
var external = fork('./test/lib/unref.js', args);
var id = setTimeout(function () {
external.kill();
done(new Error('unref subprocess timed out'));
}, 8000);
external.on('close', function (code) {
clearTimeout(id);
assert.strictEqual(code, 0);
done();
});
});
});
describe('execution order / fire query while loading', function () {
it('keep execution order for commands that may fire while redis is still loading', function (done) {
client = redis.createClient.apply(null, args);
var fired = false;
client.set('foo', 'bar', function (err, res) {
assert(fired === false);
done();
});
client.info(function (err, res) {
fired = true;
});
});
// TODO: consider allowing loading commands in v.4
// it('should fire early', function (done) {
// client = redis.createClient.apply(null, args);
// var fired = false;
// client.info(function (err, res) {
// fired = true;
// });
// client.set('foo', 'bar', function (err, res) {
// assert(fired);
// done();
// });
// assert.strictEqual(client.offline_queue.length, 1);
// assert.strictEqual(client.command_queue.length, 1);
// client.on('connect', function () {
// assert.strictEqual(client.offline_queue.length, 1);
// assert.strictEqual(client.command_queue.length, 1);
// });
// client.on('ready', function () {
// assert.strictEqual(client.offline_queue.length, 0);
// });
// });
});
describe('protocol error', function () {
it('should gracefully recover and only fail on the already send commands', function (done) {
client = redis.createClient.apply(null, args);
var error;
client.on('error', function (err) {
assert.strictEqual(err.message, 'Protocol error, got "a" as reply type byte. Please report this.');
assert.strictEqual(err, error);
assert(err instanceof redis.ParserError);
// After the hard failure work properly again. The set should have been processed properly too
client.get('foo', function (err, res) {
assert.strictEqual(res, 'bar');
done();
});
});
client.once('ready', function () {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Fatal error encountered. Command aborted. It might have been processed.');
assert.strictEqual(err.code, 'NR_FATAL');
assert(err instanceof redis.AbortError);
error = err.origin;
});
// Make sure we call execute out of the reply
// ready is called in a reply
process.nextTick(function () {
// Fail the set answer. Has no corresponding command obj and will therefore land in the error handler and set
client.reply_parser.execute(new Buffer('a*1\r*1\r$1`zasd\r\na'));
});
});
});
});
describe('enable_offline_queue', function () {
describe('true', function () {
it('does not return an error and enqueues operation', function (done) {
client = redis.createClient(9999, null);
var finished = false;
client.on('error', function (e) {
// ignore, b/c expecting a "can't connect" error
});
setTimeout(function () {
client.set('foo', 'bar', function (err, result) {
if (!finished) done(err);
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.');
});
setTimeout(function () {
assert.strictEqual(client.offline_queue.length, 1);
finished = true;
done();
}, 25);
}, 50);
});
it('enqueues operation and keep the queue while trying to reconnect', function (done) {
client = redis.createClient(9999, null, {
retry_strategy: function (options) {
if (options.attempt > 4) {
return undefined;
}
return 100;
},
});
var i = 0;
client.on('error', function (err) {
if (err.code === 'CONNECTION_BROKEN') {
assert(i, 3);
assert.strictEqual(client.offline_queue.length, 0);
assert.strictEqual(err.origin.code, 'ECONNREFUSED');
if (!(err instanceof redis.AbortError)) {
done();
} else {
assert.strictEqual(err.command, 'SET');
}
} else {
assert.equal(err.code, 'ECONNREFUSED');
if (typeof err.errno === 'number') {
// >= Node 13
assert.equal(util.getSystemErrorName(err.errno), 'ECONNREFUSED');
} else {
// < Node 13
assert.equal(err.errno, 'ECONNREFUSED');
}
assert.equal(err.syscall, 'connect');
}
});
client.on('reconnecting', function (params) {
i++;
assert.equal(params.attempt, i);
assert.strictEqual(params.times_connected, 0);
assert(params.error instanceof Error);
assert(typeof params.total_retry_time === 'number');
assert.strictEqual(client.offline_queue.length, 2);
});
// Should work with either a callback or without
client.set('baz', 13);
client.set('foo', 'bar', function (err, result) {
assert(i, 3);
assert(err);
assert.strictEqual(client.offline_queue.length, 0);
});
});
it('flushes the command queue if connection is lost', function (done) {
client = redis.createClient();
client.once('ready', function () {
var multi = client.multi();
multi.config('bar');
var cb = function (err, reply) {
assert.equal(err.code, 'UNCERTAIN_STATE');
};
for (var i = 0; i < 12; i += 3) {
client.set('foo' + i, 'bar' + i);
multi.set('foo' + (i + 1), 'bar' + (i + 1), cb);
multi.set('foo' + (i + 2), 'bar' + (i + 2));
}
multi.exec();
assert.equal(client.command_queue_length, 15);
helper.killConnection(client);
});
var end = helper.callFuncAfter(done, 3);
client.on('error', function (err) {
if (err.command === 'EXEC') {
assert.strictEqual(client.command_queue.length, 0);
assert.strictEqual(err.errors.length, 9);
assert.strictEqual(err.errors[1].command, 'SET');
assert.deepEqual(err.errors[1].args, ['foo1', 'bar1']);
end();
} else if (err.code === 'UNCERTAIN_STATE') {
assert.strictEqual(client.command_queue.length, 0);
assert.strictEqual(err.errors.length, 4);
assert.strictEqual(err.errors[0].command, 'SET');
assert.deepEqual(err.errors[0].args, ['foo0', 'bar0']);
end();
} else {
assert.equal(err.code, 'ECONNREFUSED');
if (typeof err.errno === 'number') {
// >= Node 13
assert.equal(util.getSystemErrorName(err.errno), 'ECONNREFUSED');
} else {
// < Node 13
assert.equal(err.errno, 'ECONNREFUSED');
}
assert.equal(err.syscall, 'connect');
end();
}
});
});
});
describe('false', function () {
it('stream not writable', function (done) {
client = redis.createClient({
enable_offline_queue: false
});
client.on('ready', function () {
client.stream.destroy();
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, "SET can't be processed. Stream not writeable.");
done();
});
});
});
it('emit an error and does not enqueues operation', function (done) {
client = redis.createClient(9999, null, {
max_attempts: 0,
enable_offline_queue: false
});
var end = helper.callFuncAfter(done, 3);
client.on('error', function (err) {
assert(/offline queue is deactivated|ECONNREFUSED/.test(err.message));
assert.equal(client.command_queue.length, 0);
end();
});
client.set('foo', 'bar');
assert.doesNotThrow(function () {
client.set('foo', 'bar', function (err) {
// should callback with an error
assert.ok(err);
setTimeout(end, 50);
});
});
});
it('flushes the command queue if connection is lost', function (done) {
client = redis.createClient({
enable_offline_queue: false
});
redis.debug_mode = true;
var unhookIntercept = intercept(function () {
return '';
});
client.once('ready', function () {
var multi = client.multi();
multi.config('bar');
var cb = function (err, reply) {
assert.equal(err.code, 'UNCERTAIN_STATE');
};
for (var i = 0; i < 12; i += 3) {
client.set('foo' + i, 'bar' + i);
multi.set('foo' + (i + 1), 'bar' + (i + 1), cb);
multi.set('foo' + (i + 2), 'bar' + (i + 2));
}
multi.exec();
assert.equal(client.command_queue.length, 15);
helper.killConnection(client);
});
var end = helper.callFuncAfter(done, 3);
client.on('error', function (err) {
assert.equal(client.command_queue.length, 0);
if (err.command === 'EXEC') {
assert.equal(err.errors.length, 9);
end();
} else if (err.code === 'UNCERTAIN_STATE') {
assert.equal(err.errors.length, 4);
end();
} else {
assert.equal(err.code, 'ECONNREFUSED');
if (typeof err.errno === 'number') {
// >= Node 13
assert.equal(util.getSystemErrorName(err.errno), 'ECONNREFUSED');
} else {
// < Node 13
assert.equal(err.errno, 'ECONNREFUSED');
}
assert.equal(err.syscall, 'connect');
redis.debug_mode = false;
client.end(true);
unhookIntercept();
end();
}
});
});
});
});
});
});
});
node-redis-3.0.2/test/prefix.spec.js 0000664 0000000 0000000 00000011223 13620024551 0017305 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
describe('prefix key names', function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client = null;
beforeEach(function (done) {
client = redis.createClient({
prefix: 'test:prefix:'
});
client.on('ready', function () {
client.flushdb(function (err) {
done(err);
});
});
});
afterEach(function () {
client.end(true);
});
it('auto prefix set / get', function (done) {
client.set('key', 'value', function (err, reply) {
assert.strictEqual(reply, 'OK');
});
client.get('key', function (err, reply) {
assert.strictEqual(reply, 'value');
});
client.getrange('key', 1, -1, function (err, reply) {
assert.strictEqual(reply, 'alue');
assert.strictEqual(err, null);
});
client.exists('key', function (err, res) {
assert.strictEqual(res, 1);
});
client.exists('test:prefix:key', function (err, res) {
// The key will be prefixed itself
assert.strictEqual(res, 0);
});
client.mset('key2', 'value2', 'key3', 'value3');
client.keys('*', function (err, res) {
assert.strictEqual(res.length, 3);
assert(res.indexOf('test:prefix:key') !== -1);
assert(res.indexOf('test:prefix:key2') !== -1);
assert(res.indexOf('test:prefix:key3') !== -1);
done();
});
});
it('auto prefix set / get with .batch', function (done) {
var batch = client.batch();
batch.set('key', 'value', function (err, reply) {
assert.strictEqual(reply, 'OK');
});
batch.get('key', function (err, reply) {
assert.strictEqual(reply, 'value');
});
batch.getrange('key', 1, -1, function (err, reply) {
assert.strictEqual(reply, 'alue');
assert.strictEqual(err, null);
});
batch.exists('key', function (err, res) {
assert.strictEqual(res, 1);
});
batch.exists('test:prefix:key', function (err, res) {
// The key will be prefixed itself
assert.strictEqual(res, 0);
});
batch.mset('key2', 'value2', 'key3', 'value3');
batch.keys('*', function (err, res) {
assert.strictEqual(res.length, 3);
assert(res.indexOf('test:prefix:key') !== -1);
assert(res.indexOf('test:prefix:key2') !== -1);
assert(res.indexOf('test:prefix:key3') !== -1);
});
batch.exec(done);
});
it('auto prefix set / get with .multi', function (done) {
var multi = client.multi();
multi.set('key', 'value', function (err, reply) {
assert.strictEqual(reply, 'OK');
});
multi.get('key', function (err, reply) {
assert.strictEqual(reply, 'value');
});
multi.getrange('key', 1, -1, function (err, reply) {
assert.strictEqual(reply, 'alue');
assert.strictEqual(err, null);
});
multi.exists('key', function (err, res) {
assert.strictEqual(res, 1);
});
multi.exists('test:prefix:key', function (err, res) {
// The key will be prefixed itself
assert.strictEqual(res, 0);
});
multi.mset('key2', 'value2', 'key3', 'value3');
multi.keys('*', function (err, res) {
assert.strictEqual(res.length, 3);
assert(res.indexOf('test:prefix:key') !== -1);
assert(res.indexOf('test:prefix:key2') !== -1);
assert(res.indexOf('test:prefix:key3') !== -1);
});
multi.exec(done);
});
});
});
});
node-redis-3.0.2/test/pubsub.spec.js 0000664 0000000 0000000 00000074047 13620024551 0017325 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
describe('publish/subscribe', function () {
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var pub = null;
var sub = null;
var channel = 'test channel';
var channel2 = 'test channel 2';
var message = 'test message';
beforeEach(function (done) {
var end = helper.callFuncAfter(done, 2);
pub = redis.createClient.apply(null, args);
sub = redis.createClient.apply(null, args);
pub.once('connect', function () {
pub.flushdb(function () {
end();
});
});
sub.once('connect', function () {
end();
});
});
describe('disable resubscribe', function () {
beforeEach(function (done) {
sub.end(false);
sub = redis.createClient({
disable_resubscribing: true
});
sub.once('connect', function () {
done();
});
});
it('does not fire subscribe events after reconnecting', function (done) {
var a = false;
sub.on('subscribe', function (chnl, count) {
if (chnl === channel2) {
if (a) {
return done(new Error('Test failed'));
}
assert.equal(2, count);
sub.stream.destroy();
}
});
sub.on('reconnecting', function () {
a = true;
sub.on('ready', function () {
assert.strictEqual(sub.command_queue.length, 0);
done();
});
});
sub.subscribe(channel, channel2);
});
});
describe('string_numbers and pub sub', function () {
beforeEach(function (done) {
sub.end(false);
sub = redis.createClient({
string_numbers: true
});
sub.once('connect', function () {
done();
});
});
it('does not fire subscribe events after reconnecting', function (done) {
var i = 0;
var end = helper.callFuncAfter(done, 2);
sub.on('subscribe', function (chnl, count) {
assert.strictEqual(typeof count, 'number');
assert.strictEqual(++i, count);
});
sub.on('unsubscribe', function (chnl, count) {
assert.strictEqual(typeof count, 'number');
assert.strictEqual(--i, count);
});
sub.subscribe(channel, channel2);
sub.unsubscribe(function (err, res) { // Do not pass a channel here!
assert.strictEqual(sub.pub_sub_mode, 2);
assert.deepEqual(sub.subscription_set, {});
end();
});
sub.set('foo', 'bar', helper.isString('OK'));
sub.subscribe(channel2, end);
});
});
describe('subscribe', function () {
it('fires a subscribe event for each channel subscribed to even after reconnecting', function (done) {
var a = false;
sub.on('subscribe', function (chnl, count) {
if (chnl === channel2) {
assert.equal(2, count);
if (a) return done();
sub.stream.destroy();
}
});
sub.on('reconnecting', function () {
a = true;
});
sub.subscribe(channel, channel2);
});
it('fires a subscribe event for each channel as buffer subscribed to even after reconnecting', function (done) {
var a = false;
sub.end(true);
sub = redis.createClient({
detect_buffers: true
});
sub.on('subscribe', function (chnl, count) {
if (chnl.inspect() === new Buffer([0xAA, 0xBB, 0x00, 0xF0]).inspect()) {
assert.equal(1, count);
if (a) {
return done();
}
sub.stream.destroy();
}
});
sub.on('reconnecting', function () {
a = true;
});
sub.subscribe(new Buffer([0xAA, 0xBB, 0x00, 0xF0]), channel2);
});
it('receives messages on subscribed channel', function (done) {
var end = helper.callFuncAfter(done, 2);
sub.on('subscribe', function (chnl, count) {
pub.publish(channel, message, function (err, res) {
helper.isNumber(1)(err, res);
end();
});
});
sub.on('message', function (chnl, msg) {
assert.equal(chnl, channel);
assert.equal(msg, message);
end();
});
sub.subscribe(channel);
});
it('receives messages if subscribe is called after unsubscribe', function (done) {
var end = helper.callFuncAfter(done, 2);
sub.once('subscribe', function (chnl, count) {
pub.publish(channel, message, function (err, res) {
helper.isNumber(1)(err, res);
end();
});
});
sub.on('message', function (chnl, msg) {
assert.equal(chnl, channel);
assert.equal(msg, message);
end();
});
sub.subscribe(channel);
sub.unsubscribe(channel);
sub.subscribe(channel);
});
it('handles SUB_UNSUB_MSG_SUB', function (done) {
sub.subscribe('chan8');
sub.subscribe('chan9');
sub.unsubscribe('chan9');
pub.publish('chan8', 'something');
sub.subscribe('chan9', done);
});
it('handles SUB_UNSUB_MSG_SUB 2', function (done) {
sub.psubscribe('abc*', helper.isString('abc*'));
sub.subscribe('xyz');
sub.unsubscribe('xyz');
pub.publish('abcd', 'something');
sub.subscribe('xyz', done);
});
it('emits end event if quit is called from within subscribe', function (done) {
sub.on('end', done);
sub.on('subscribe', function (chnl, count) {
sub.quit();
});
sub.subscribe(channel);
});
it('subscribe; close; resubscribe with prototype inherited property names', function (done) {
var count = 0;
var channels = ['channel 1', 'channel 2'];
var msg = ['hi from channel 1', 'hi from channel 2'];
sub.on('message', function (channel, message) {
var n = Math.max(count - 1, 0);
assert.strictEqual(channel, channels[n]);
assert.strictEqual(message, msg[n]);
if (count === 2) return done();
sub.stream.end();
});
sub.select(3);
sub.subscribe(channels);
sub.on('ready', function (err, results) {
pub.publish(channels[count], msg[count]);
count++;
});
pub.publish(channels[count], msg[count]);
});
});
describe('multiple subscribe / unsubscribe commands', function () {
it('reconnects properly with pub sub and select command', function (done) {
var end = helper.callFuncAfter(done, 2);
sub.select(3);
sub.set('foo', 'bar');
sub.set('failure', helper.isError()); // Triggering a warning while subscribing should work
sub.mget('foo', 'bar', 'baz', 'hello', 'world', function (err, res) {
assert.deepEqual(res, ['bar', null, null, null, null]);
});
sub.subscribe('somechannel', 'another channel', function (err, res) {
end();
sub.stream.destroy();
});
assert(sub.ready);
sub.on('ready', function () {
sub.unsubscribe();
sub.del('foo');
sub.info(end);
});
});
it('should not go into pubsub mode with unsubscribe commands', function (done) {
sub.on('unsubscribe', function (msg) {
// The unsubscribe should not be triggered, as there was no corresponding channel
throw new Error('Test failed');
});
sub.set('foo', 'bar');
sub.unsubscribe(function (err, res) {
assert.strictEqual(res, null);
});
sub.del('foo', done);
});
it('handles multiple channels with the same channel name properly, even with buffers', function (done) {
var channels = ['a', 'b', 'a', new Buffer('a'), 'c', 'b'];
var subscribed_channels = [1, 2, 2, 2, 3, 3];
var i = 0;
sub.subscribe(channels);
sub.on('subscribe', function (channel, count) {
if (Buffer.isBuffer(channel)) {
assert.strictEqual(channel.inspect(), new Buffer(channels[i]).inspect());
} else {
assert.strictEqual(channel, channels[i].toString());
}
assert.strictEqual(count, subscribed_channels[i]);
i++;
});
sub.unsubscribe('a', 'c', 'b');
sub.get('foo', done);
});
it('should only resubscribe to channels not unsubscribed earlier on a reconnect', function (done) {
sub.subscribe('/foo', '/bar');
sub.batch().unsubscribe(['/bar'], function () {
pub.pubsub('channels', function (err, res) {
assert.deepEqual(res, ['/foo']);
sub.stream.destroy();
sub.once('ready', function () {
pub.pubsub('channels', function (err, res) {
assert.deepEqual(res, ['/foo']);
sub.unsubscribe('/foo', done);
});
});
});
}).exec();
});
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Withouth callbacks', function (done) {
function subscribe (channels) {
sub.unsubscribe(helper.isNull);
sub.subscribe(channels, helper.isNull);
}
var all = false;
var subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
sub.on('subscribe', function (msg, count) {
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
if (subscribeMsg.length === 0 && all) {
assert.strictEqual(count, 3);
done();
}
});
var unsubscribeMsg = ['1', '3', '2'];
sub.on('unsubscribe', function (msg, count) {
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1);
if (unsubscribeMsg.length === 0) {
assert.strictEqual(count, 0);
all = true;
}
});
subscribe(['1', '3']);
subscribe(['2']);
subscribe(['5', 'test', 'bla']);
});
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callbacks', function (done) {
function subscribe (channels) {
sub.unsubscribe();
sub.subscribe(channels);
}
var all = false;
var subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
sub.on('subscribe', function (msg, count) {
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
if (subscribeMsg.length === 0 && all) {
assert.strictEqual(count, 3);
done();
}
});
var unsubscribeMsg = ['1', '3', '2'];
sub.on('unsubscribe', function (msg, count) {
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1);
if (unsubscribeMsg.length === 0) {
assert.strictEqual(count, 0);
all = true;
}
});
subscribe(['1', '3']);
subscribe(['2']);
subscribe(['5', 'test', 'bla']);
});
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callback and concret channels', function (done) {
function subscribe (channels) {
sub.unsubscribe(channels);
sub.unsubscribe(channels);
sub.subscribe(channels);
}
var all = false;
var subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
sub.on('subscribe', function (msg, count) {
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
if (subscribeMsg.length === 0 && all) {
assert.strictEqual(count, 6);
done();
}
});
var unsubscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
sub.on('unsubscribe', function (msg, count) {
var pos = unsubscribeMsg.indexOf(msg);
if (pos !== -1)
unsubscribeMsg.splice(pos, 1);
if (unsubscribeMsg.length === 0) {
all = true;
}
});
subscribe(['1', '3']);
subscribe(['2']);
subscribe(['5', 'test', 'bla']);
});
it('unsubscribes, subscribes, unsubscribes... with pattern matching', function (done) {
function subscribe (channels, callback) {
sub.punsubscribe('prefix:*', helper.isNull);
sub.psubscribe(channels, function (err, res) {
helper.isNull(err);
if (callback) callback(err, res);
});
}
var all = false;
var end = helper.callFuncAfter(done, 8);
var subscribeMsg = ['prefix:*', 'prefix:3', 'prefix:2', '5', 'test:a', 'bla'];
sub.on('psubscribe', function (msg, count) {
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
if (subscribeMsg.length === 0) {
assert.strictEqual(count, 5);
all = true;
}
});
var rest = 1;
var unsubscribeMsg = ['prefix:*', 'prefix:*', 'prefix:*', '*'];
sub.on('punsubscribe', function (msg, count) {
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1);
if (all) {
assert.strictEqual(unsubscribeMsg.length, 0);
assert.strictEqual(count, rest--); // Print the remaining channels
end();
} else {
assert.strictEqual(msg, 'prefix:*');
assert.strictEqual(count, rest++ - 1);
}
});
sub.on('pmessage', function (pattern, channel, msg) {
assert.strictEqual(msg, 'test');
assert.strictEqual(pattern, 'prefix:*');
assert.strictEqual(channel, 'prefix:1');
end();
});
subscribe(['prefix:*', 'prefix:3'], function () {
pub.publish('prefix:1', new Buffer('test'), function () {
subscribe(['prefix:2']);
subscribe(['5', 'test:a', 'bla'], function () {
assert(all);
});
sub.punsubscribe(function (err, res) {
assert(!err);
assert.strictEqual(res, 'bla');
assert(all);
all = false; // Make sure the callback is actually after the emit
end();
});
sub.pubsub('channels', function (err, res) {
assert.strictEqual(res.length, 0);
end();
});
});
});
});
});
describe('unsubscribe', function () {
it('fires an unsubscribe event', function (done) {
sub.on('subscribe', function (chnl, count) {
sub.unsubscribe(channel);
});
sub.subscribe(channel);
sub.on('unsubscribe', function (chnl, count) {
assert.equal(chnl, channel);
assert.strictEqual(count, 0);
return done();
});
});
it('puts client back into write mode', function (done) {
sub.on('subscribe', function (chnl, count) {
sub.unsubscribe(channel);
});
sub.subscribe(channel);
sub.on('unsubscribe', function (chnl, count) {
pub.incr('foo', helper.isNumber(1, done));
});
});
it('does not complain when unsubscribe is called and there are no subscriptions', function (done) {
sub.unsubscribe(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res, null);
done();
});
});
it('executes callback when unsubscribe is called and there are no subscriptions', function (done) {
pub.unsubscribe(function (err, results) {
assert.strictEqual(null, results);
done(err);
});
});
});
describe('psubscribe', function () {
it('allows all channels to be subscribed to using a * pattern', function (done) {
sub.subscribe('/foo');
var sub2 = redis.createClient({
return_buffers: true
});
sub2.on('ready', function () {
sub2.batch().psubscribe('*', helper.isString('*')).exec();
sub2.subscribe('/foo');
sub2.on('pmessage', function (pattern, channel, message) {
assert.strictEqual(pattern.inspect(), new Buffer('*').inspect());
assert.strictEqual(channel.inspect(), new Buffer('/foo').inspect());
assert.strictEqual(message.inspect(), new Buffer('hello world').inspect());
sub2.quit(done);
});
pub.pubsub('numsub', '/foo', function (err, res) {
assert.deepEqual(res, ['/foo', 2]);
});
// sub2 is counted twice as it subscribed with psubscribe and subscribe
pub.publish('/foo', 'hello world', helper.isNumber(3));
});
});
it('allows to listen to pmessageBuffer and pmessage', function (done) {
var end = helper.callFuncAfter(done, 6);
var data = Array(10000).join('äüs^öéÉÉ`e');
sub.set('foo', data, function () {
sub.get('foo');
sub.stream.once('data', function () {
assert.strictEqual(sub.message_buffers, false);
assert.strictEqual(sub.shouldBuffer, false);
sub.on('pmessageBuffer', function (pattern, channel) {
if (typeof pattern === 'string') {
pattern = new Buffer(pattern);
channel = new Buffer(channel);
}
assert.strictEqual(pattern.inspect(), new Buffer('*').inspect());
assert.strictEqual(channel.inspect(), new Buffer('/foo').inspect());
sub.quit(end);
});
// Either message_buffers or buffers has to be true, but not both at the same time
assert.notStrictEqual(sub.message_buffers, sub.buffers);
});
var batch = sub.batch();
batch.psubscribe('*');
batch.subscribe('/foo');
batch.unsubscribe('/foo');
batch.unsubscribe(helper.isNull());
batch.subscribe(['/foo'], helper.isString('/foo'));
batch.exec(function () {
pub.pubsub('numsub', '/foo', function (err, res) {
// There's one subscriber to this channel
assert.deepEqual(res, ['/foo', 1]);
end();
});
pub.pubsub('channels', function (err, res) {
// There's exactly one channel that is listened too
assert.deepEqual(res, ['/foo']);
end();
});
pub.pubsub('numpat', function (err, res) {
// One pattern is active
assert.strictEqual(res, 1);
end();
});
pub.publish('/foo', 'hello world', helper.isNumber(2));
});
// Either message_buffers or buffers has to be true, but not both at the same time
sub.on('pmessage', function (pattern, channel, message) {
assert.strictEqual(pattern, '*');
assert.strictEqual(channel, '/foo');
assert.strictEqual(message, 'hello world');
end();
});
sub.on('message', function (channel, message) {
assert.strictEqual(channel, '/foo');
assert.strictEqual(message, 'hello world');
end();
});
});
});
});
describe('punsubscribe', function () {
it('does not complain when punsubscribe is called and there are no subscriptions', function () {
sub.punsubscribe();
});
it('executes callback when punsubscribe is called and there are no subscriptions', function (done) {
pub.batch().punsubscribe(helper.isNull()).exec(done);
});
});
describe('fail for other commands while in pub sub mode', function () {
it('return error if only pub sub commands are allowed', function (done) {
sub.subscribe('channel');
// Ping is allowed even if not listed as such!
sub.ping(function (err, res) {
assert.strictEqual(err, null);
assert.strictEqual(res[0], 'pong');
});
// Get is forbidden
sub.get('foo', function (err, res) {
assert(/^ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message));
assert.strictEqual(err.command, 'GET');
});
// Quit is allowed
sub.quit(done);
});
it('emit error if only pub sub commands are allowed without callback', function (done) {
sub.subscribe('channel');
sub.on('error', function (err) {
assert(/^ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message));
assert.strictEqual(err.command, 'GET');
done();
});
sub.get('foo');
});
});
it('should not publish a message multiple times per command', function (done) {
var published = {};
function subscribe (message) {
sub.removeAllListeners('subscribe');
sub.removeAllListeners('message');
sub.removeAllListeners('unsubscribe');
sub.on('subscribe', function () {
pub.publish('/foo', message);
});
sub.on('message', function (channel, message) {
if (published[message]) {
done(new Error('Message published more than once.'));
}
published[message] = true;
});
sub.on('unsubscribe', function (channel, count) {
assert.strictEqual(count, 0);
});
sub.subscribe('/foo');
}
subscribe('hello');
setTimeout(function () {
sub.unsubscribe();
setTimeout(function () {
subscribe('world');
setTimeout(done, 50);
}, 40);
}, 40);
});
it('should not publish a message without any publish command', function (done) {
pub.set('foo', 'message');
pub.set('bar', 'hello');
pub.mget('foo', 'bar');
pub.subscribe('channel', function () {
setTimeout(done, 50);
});
pub.on('message', function (msg) {
done(new Error('This message should not have been published: ' + msg));
});
});
it('arguments variants', function (done) {
sub.batch()
.info(['stats'])
.info()
.client('KILL', ['type', 'pubsub'])
.client('KILL', ['type', 'pubsub'], function () {})
.unsubscribe()
.psubscribe(['pattern:*'])
.punsubscribe('unkown*')
.punsubscribe(['pattern:*'])
.exec(function (err, res) {
sub.client('kill', ['type', 'pubsub']);
sub.psubscribe('*');
sub.punsubscribe('pa*');
sub.punsubscribe(['a', '*'], done);
});
});
afterEach(function () {
// Explicitly ignore still running commands
pub.end(false);
sub.end(false);
});
});
});
});
node-redis-3.0.2/test/rename.spec.js 0000664 0000000 0000000 00000012216 13620024551 0017262 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
if (process.platform === 'win32') {
// TODO: Fix redis process spawn on windows
return;
}
describe('rename commands', function () {
before(function (done) {
helper.stopRedis(function () {
helper.startRedis('./conf/rename.conf', done);
});
});
helper.allTests(function (ip, args) {
describe('using ' + ip, function () {
var client = null;
beforeEach(function (done) {
if (helper.redisProcess().spawnFailed()) return done();
client = redis.createClient({
rename_commands: {
set: '807081f5afa96845a02816a28b7258c3',
GETRANGE: '9e3102b15cf231c4e9e940f284744fe0'
},
});
client.on('ready', function () {
client.flushdb(done);
});
});
afterEach(function () {
if (helper.redisProcess().spawnFailed()) return;
client.end(true);
});
it('allows to use renamed functions', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.set('key', 'value', function (err, reply) {
assert.strictEqual(reply, 'OK');
});
client.get('key', function (err, reply) {
assert.strictEqual(err.message, 'ERR unknown command `get`, with args beginning with: `key`, ');
assert.strictEqual(err.command, 'GET');
assert.strictEqual(reply, undefined);
});
client.getrange('key', 1, -1, function (err, reply) {
assert.strictEqual(reply, 'alue');
assert.strictEqual(err, null);
done();
});
});
it('should also work with batch', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.batch([['set', 'key', 'value']]).exec(function (err, res) {
assert.strictEqual(res[0], 'OK');
});
var batch = client.batch();
batch.getrange('key', 1, -1);
batch.exec(function (err, res) {
assert(!err);
assert.strictEqual(res.length, 1);
assert.strictEqual(res[0], 'alue');
done();
});
});
it('should also work with multi', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.multi([['set', 'key', 'value']]).exec(function (err, res) {
assert.strictEqual(res[0], 'OK');
});
var multi = client.multi();
multi.getrange('key', 1, -1);
multi.exec(function (err, res) {
assert(!err);
assert.strictEqual(res.length, 1);
assert.strictEqual(res[0], 'alue');
done();
});
});
it('should also work with multi and abort transaction', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var multi = client.multi();
multi.get('key');
multi.getrange('key', 1, -1, function (err, reply) {
assert.strictEqual(reply, 'alue');
assert.strictEqual(err, null);
});
multi.exec(function (err, res) {
assert(err);
assert.strictEqual(err.message, 'EXECABORT Transaction discarded because of previous errors.');
assert.strictEqual(err.errors[0].message, 'ERR unknown command `get`, with args beginning with: `key`, ');
assert.strictEqual(err.errors[0].command, 'GET');
assert.strictEqual(err.code, 'EXECABORT');
assert.strictEqual(err.errors[0].code, 'ERR');
done();
});
});
it('should also work prefixed commands', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.end(true);
client = redis.createClient({
rename_commands: {
set: '807081f5afa96845a02816a28b7258c3'
},
prefix: 'baz'
});
client.set('foo', 'bar');
client.keys('*', function (err, reply) {
assert.strictEqual(reply[0], 'bazfoo');
assert.strictEqual(err, null);
done();
});
});
});
});
after(function (done) {
if (helper.redisProcess().spawnFailed()) return done();
helper.stopRedis(function () {
helper.startRedis('./conf/redis.conf', done);
});
});
});
node-redis-3.0.2/test/return_buffers.spec.js 0000664 0000000 0000000 00000036344 13620024551 0021056 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
describe('return_buffers', function () {
helper.allTests(function (ip, basicArgs) {
describe('using ' + ip, function () {
var client;
var args = config.configureClient(ip, {
return_buffers: true,
detect_buffers: true
});
beforeEach(function (done) {
client = redis.createClient.apply(null, args);
var i = 1;
if (args[2].detect_buffers) {
// Test if detect_buffer option was deactivated
assert.strictEqual(client.options.detect_buffers, false);
args[2].detect_buffers = false;
i++;
}
var end = helper.callFuncAfter(done, i);
client.on('warning', function (msg) {
assert.strictEqual(msg, 'WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
end();
});
client.once('error', done);
client.once('connect', function () {
client.flushdb(function (err) {
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2');
client.set('string key 1', 'string value');
end(err);
});
});
});
describe('get', function () {
describe('first argument is a string', function () {
it('returns a buffer', function (done) {
client.get('string key 1', function (err, reply) {
assert.strictEqual(true, Buffer.isBuffer(reply));
assert.strictEqual('', reply.inspect());
return done(err);
});
});
it('returns a bufffer when executed as part of transaction', function (done) {
client.multi().get('string key 1').exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]));
assert.strictEqual('', reply[0].inspect());
return done(err);
});
});
});
});
describe('multi.hget', function () {
it('returns buffers', function (done) {
client.multi()
.hget('hash key 2', 'key 1')
.hget(new Buffer('hash key 2'), 'key 1')
.hget('hash key 2', new Buffer('key 2'))
.hget('hash key 2', 'key 2')
.exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(4, reply.length);
assert.strictEqual('', reply[0].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[1]));
assert.strictEqual('', reply[1].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[2]));
assert.strictEqual('', reply[2].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[3]));
assert.strictEqual('', reply[3].inspect());
return done(err);
});
});
});
describe('batch.hget', function () {
it('returns buffers', function (done) {
client.batch()
.hget('hash key 2', 'key 1')
.hget(new Buffer('hash key 2'), 'key 1')
.hget('hash key 2', new Buffer('key 2'))
.hget('hash key 2', 'key 2')
.exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(4, reply.length);
assert.strictEqual('', reply[0].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[1]));
assert.strictEqual('', reply[1].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[2]));
assert.strictEqual('', reply[2].inspect());
assert.strictEqual(true, Buffer.isBuffer(reply[3]));
assert.strictEqual('', reply[3].inspect());
return done(err);
});
});
});
describe('hmget', function () {
describe('first argument is a string', function () {
it('handles array of strings with undefined values in transaction (repro #344)', function (done) {
client.multi().hmget('hash key 2', 'key 3', 'key 4').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.equal(null, reply[0][0]);
assert.equal(null, reply[0][1]);
return done(err);
});
});
});
describe('first argument is a buffer', function () {
it('returns buffers for keys requested', function (done) {
client.hmget(new Buffer('hash key 2'), 'key 1', 'key 2', function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(2, reply.length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]));
assert.strictEqual(true, Buffer.isBuffer(reply[1]));
assert.strictEqual('', reply[0].inspect());
assert.strictEqual('', reply[1].inspect());
return done(err);
});
});
it('returns buffers for keys requested in transaction', function (done) {
client.multi().hmget(new Buffer('hash key 2'), 'key 1', 'key 2').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.strictEqual(true, Buffer.isBuffer(reply[0][0]));
assert.strictEqual(true, Buffer.isBuffer(reply[0][1]));
assert.strictEqual('', reply[0][0].inspect());
assert.strictEqual('', reply[0][1].inspect());
return done(err);
});
});
it('returns buffers for keys requested in .batch', function (done) {
client.batch().hmget(new Buffer('hash key 2'), 'key 1', 'key 2').exec(function (err, reply) {
assert.strictEqual(true, Array.isArray(reply));
assert.strictEqual(1, reply.length);
assert.strictEqual(2, reply[0].length);
assert.strictEqual(true, Buffer.isBuffer(reply[0][0]));
assert.strictEqual(true, Buffer.isBuffer(reply[0][1]));
assert.strictEqual('', reply[0][0].inspect());
assert.strictEqual('', reply[0][1].inspect());
return done(err);
});
});
});
});
describe('hgetall', function (done) {
describe('first argument is a string', function () {
it('returns buffer values', function (done) {
client.hgetall('hash key 2', function (err, reply) {
assert.strictEqual('object', typeof reply);
assert.strictEqual(2, Object.keys(reply).length);
assert.strictEqual('', reply['key 1'].inspect());
assert.strictEqual('', reply['key 2'].inspect());
return done(err);
});
});
it('returns buffer values when executed in transaction', function (done) {
client.multi().hgetall('hash key 2').exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual('', reply[0]['key 1'].inspect());
assert.strictEqual('', reply[0]['key 2'].inspect());
return done(err);
});
});
it('returns buffer values when executed in .batch', function (done) {
client.batch().hgetall('hash key 2').exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual('', reply[0]['key 1'].inspect());
assert.strictEqual('', reply[0]['key 2'].inspect());
return done(err);
});
});
});
describe('first argument is a buffer', function () {
it('returns buffer values', function (done) {
client.hgetall(new Buffer('hash key 2'), function (err, reply) {
assert.strictEqual(null, err);
assert.strictEqual('object', typeof reply);
assert.strictEqual(2, Object.keys(reply).length);
assert.strictEqual(true, Buffer.isBuffer(reply['key 1']));
assert.strictEqual(true, Buffer.isBuffer(reply['key 2']));
assert.strictEqual('', reply['key 1'].inspect());
assert.strictEqual('', reply['key 2'].inspect());
return done(err);
});
});
it('returns buffer values when executed in transaction', function (done) {
client.multi().hgetall(new Buffer('hash key 2')).exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 1']));
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']));
assert.strictEqual('', reply[0]['key 1'].inspect());
assert.strictEqual('', reply[0]['key 2'].inspect());
return done(err);
});
});
it('returns buffer values when executed in .batch', function (done) {
client.batch().hgetall(new Buffer('hash key 2')).exec(function (err, reply) {
assert.strictEqual(1, reply.length);
assert.strictEqual('object', typeof reply[0]);
assert.strictEqual(2, Object.keys(reply[0]).length);
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 1']));
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']));
assert.strictEqual('', reply[0]['key 1'].inspect());
assert.strictEqual('', reply[0]['key 2'].inspect());
return done(err);
});
});
});
});
describe('publish/subscribe', function (done) {
var pub;
var sub;
var channel = 'test channel';
var message = new Buffer('test message');
var args = config.configureClient(ip, {
return_buffers: true
});
beforeEach(function (done) {
var pubConnected;
var subConnected;
pub = redis.createClient.apply(redis.createClient, basicArgs);
sub = redis.createClient.apply(null, args);
pub.once('connect', function () {
pub.flushdb(function () {
pubConnected = true;
if (subConnected) {
done();
}
});
});
sub.once('connect', function () {
subConnected = true;
if (pubConnected) {
done();
}
});
});
it('receives buffer messages', function (done) {
sub.on('subscribe', function (chnl, count) {
pub.publish(channel, message);
});
sub.on('message', function (chnl, msg) {
assert.strictEqual(true, Buffer.isBuffer(msg));
assert.strictEqual('', msg.inspect());
return done();
});
sub.subscribe(channel);
});
afterEach(function () {
sub.end(true);
pub.end(true);
});
});
});
});
});
node-redis-3.0.2/test/tls.spec.js 0000664 0000000 0000000 00000012633 13620024551 0016620 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var config = require('./lib/config');
var fs = require('fs');
var helper = require('./helper');
var path = require('path');
var redis = config.redis;
var utils = require('../lib/utils');
var tls = require('tls');
var tls_options = {
servername: 'redis.js.org',
rejectUnauthorized: true,
ca: [ String(fs.readFileSync(path.resolve(__dirname, './conf/redis.js.org.cert'))) ]
};
var tls_port = 6380;
// Use skip instead of returning to indicate what tests really got skipped
var skip = false;
describe('TLS connection tests', function () {
before(function (done) {
// Print the warning when the tests run instead of while starting mocha
if (process.platform === 'win32') {
skip = true;
console.warn('\nStunnel tests do not work on windows atm. If you think you can fix that, it would be warmly welcome.\n');
}
if (skip) return done();
helper.stopStunnel(function () {
helper.startStunnel(done);
});
});
after(function (done) {
if (skip) return done();
helper.stopStunnel(done);
});
var client;
afterEach(function () {
if (skip) return;
client.end(true);
});
describe('on lost connection', function () {
it('emit an error after max retry timeout and do not try to reconnect afterwards', function (done) {
if (skip) this.skip();
var connect_timeout = 500; // in ms
client = redis.createClient({
connect_timeout: connect_timeout,
port: tls_port,
tls: utils.clone(tls_options)
});
var time = 0;
assert.strictEqual(client.address, '127.0.0.1:' + tls_port);
client.once('ready', function () {
helper.killConnection(client);
});
client.on('reconnecting', function (params) {
time += params.delay;
});
client.on('error', function (err) {
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
process.nextTick(function () {
assert.strictEqual(time, connect_timeout);
assert.strictEqual(client.emitted_end, true);
assert.strictEqual(client.connected, false);
assert.strictEqual(client.ready, false);
assert.strictEqual(client.closing, true);
assert.strictEqual(time, connect_timeout);
done();
});
}
});
});
});
describe('when not connected', function () {
it('connect with host and port provided in the tls object', function (done) {
if (skip) this.skip();
var tls_opts = utils.clone(tls_options);
tls_opts.port = tls_port;
tls_opts.host = 'localhost';
client = redis.createClient({
connect_timeout: 1000,
tls: tls_opts
});
// verify connection is using TCP, not UNIX socket
assert.strictEqual(client.connection_options.host, 'localhost');
assert.strictEqual(client.connection_options.port, tls_port);
assert.strictEqual(client.address, 'localhost:' + tls_port);
assert(client.stream.encrypted);
client.set('foo', 'bar');
client.get('foo', helper.isString('bar', done));
});
describe('using rediss as url protocol', function () {
var tls_connect = tls.connect;
beforeEach(function () {
tls.connect = function (options) {
options = utils.clone(options);
options.ca = tls_options.ca;
options.servername = 'redis.js.org';
options.rejectUnauthorized = true;
return tls_connect.call(tls, options);
};
});
afterEach(function () {
tls.connect = tls_connect;
});
it('connect with tls when rediss is used as the protocol', function (done) {
if (skip) this.skip();
client = redis.createClient('rediss://localhost:' + tls_port);
// verify connection is using TCP, not UNIX socket
assert(client.stream.encrypted);
client.set('foo', 'bar');
client.get('foo', helper.isString('bar', done));
});
});
it('fails to connect because the cert is not correct', function (done) {
if (skip) this.skip();
var faulty_cert = utils.clone(tls_options);
faulty_cert.ca = [ String(fs.readFileSync(path.resolve(__dirname, './conf/faulty.cert'))) ];
client = redis.createClient({
host: 'localhost',
connect_timeout: 1000,
port: tls_port,
tls: faulty_cert
});
assert.strictEqual(client.address, 'localhost:' + tls_port);
client.on('error', function (err) {
assert(/DEPTH_ZERO_SELF_SIGNED_CERT/.test(err.code || err.message), err);
client.end(true);
});
client.set('foo', 'bar', function (err, res) {
done(res);
});
});
});
});
node-redis-3.0.2/test/unify_options.spec.js 0000664 0000000 0000000 00000023313 13620024551 0020720 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var unifyOptions = require('../lib/createClient');
var intercept = require('intercept-stdout');
describe('createClient options', function () {
describe('port as first parameter', function () {
it('pass the options in the second parameter after a port', function () {
var options = unifyOptions(1234, {
option1: true,
option2: function () {}
});
assert.strictEqual(Object.keys(options).length, 4);
assert(options.option1);
assert.strictEqual(options.port, 1234);
assert.strictEqual(options.host, undefined);
assert.strictEqual(typeof options.option2, 'function');
});
it('pass the options in the third parameter after a port and host being set to null', function () {
var options = unifyOptions(1234, null, {
option1: true,
option2: function () {}
});
assert.strictEqual(Object.keys(options).length, 4);
assert(options.option1);
assert.strictEqual(options.port, 1234);
assert.strictEqual(options.host, undefined);
assert.strictEqual(typeof options.option2, 'function');
});
it('pass the options in the third parameter after a port and host being set to undefined', function () {
var options = unifyOptions(1234, undefined, {
option1: true,
option2: function () {}
});
assert.strictEqual(Object.keys(options).length, 4);
assert(options.option1);
assert.strictEqual(options.port, 1234);
assert.strictEqual(options.host, undefined);
assert.strictEqual(typeof options.option2, 'function');
});
it('pass the options in the third parameter after a port and host', function () {
var options = unifyOptions('1234', 'localhost', {
option1: true,
option2: function () {}
});
assert.strictEqual(Object.keys(options).length, 4);
assert(options.option1);
assert.strictEqual(options.port, '1234');
assert.strictEqual(options.host, 'localhost');
assert.strictEqual(typeof options.option2, 'function');
});
it('should throw with three parameters all set to a truthy value', function () {
try {
unifyOptions(1234, {}, {});
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Unknown type of connection in createClient()');
}
});
});
describe('unix socket as first parameter', function () {
it('pass the options in the second parameter after a port', function () {
var options = unifyOptions('/tmp/redis.sock', {
option1: true,
option2: function () {},
option3: [1, 2, 3]
});
assert.strictEqual(Object.keys(options).length, 4);
assert(options.option1);
assert.strictEqual(options.path, '/tmp/redis.sock');
assert.strictEqual(typeof options.option2, 'function');
assert.strictEqual(options.option3.length, 3);
});
it('pass the options in the third parameter after a port and host being set to null', function () {
var options = unifyOptions('/tmp/redis.sock', null, {
option1: true,
option2: function () {}
});
assert.strictEqual(Object.keys(options).length, 3);
assert(options.option1);
assert.strictEqual(options.path, '/tmp/redis.sock');
assert.strictEqual(typeof options.option2, 'function');
});
});
describe('redis url as first parameter', function () {
it('empty redis url including options as second parameter', function () {
var options = unifyOptions('redis://', {
option: [1, 2, 3]
});
assert.strictEqual(Object.keys(options).length, 1);
assert.strictEqual(options.option.length, 3);
});
it('begin with two slashes including options as third parameter', function () {
var options = unifyOptions('//:abc@/3?port=123', {
option: [1, 2, 3]
});
assert.strictEqual(Object.keys(options).length, 4);
assert.strictEqual(options.option.length, 3);
assert.strictEqual(options.port, '123');
assert.strictEqual(options.db, '3');
assert.strictEqual(options.password, 'abc');
});
it('duplicated, identical query options including options obj', function () {
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
return '';
});
var options = unifyOptions('//:abc@localhost:123/3?db=3&port=123&password=abc', null, {
option: [1, 2, 3]
});
unhookIntercept();
assert.strictEqual(text,
'node_redis: WARNING: You passed the db option twice!\n' +
'node_redis: WARNING: You passed the port option twice!\n' +
'node_redis: WARNING: You passed the password option twice!\n'
);
assert.strictEqual(Object.keys(options).length, 5);
assert.strictEqual(options.option.length, 3);
assert.strictEqual(options.host, 'localhost');
assert.strictEqual(options.port, '123');
assert.strictEqual(options.db, '3');
assert.strictEqual(options.password, 'abc');
});
it('should throw on duplicated, non-identical query options', function () {
try {
unifyOptions('//:abc@localhost:1234/3?port=123&password=abc');
throw new Error('failed');
} catch (err) {
assert.equal(err.message, 'The port option is added twice and does not match');
}
});
it('should throw without protocol slashes', function () {
try {
unifyOptions('redis:abc@localhost:123/3?db=3&port=123&password=abc');
throw new Error('failed');
} catch (err) {
assert.equal(err.message, 'The redis url must begin with slashes "//" or contain slashes after the redis protocol');
}
});
it('warns on protocol other than redis in the redis url', function () {
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
return '';
});
var options = unifyOptions('http://abc');
unhookIntercept();
assert.strictEqual(Object.keys(options).length, 1);
assert.strictEqual(options.host, 'abc');
assert.strictEqual(text, 'node_redis: WARNING: You passed "http" as protocol instead of the "redis" protocol!\n');
});
});
describe('no parameters or set to null / undefined', function () {
it('no parameters', function () {
var options = unifyOptions();
assert.strictEqual(Object.keys(options).length, 1);
assert.strictEqual(options.host, undefined);
});
it('set to null', function () {
var options = unifyOptions(null, null);
assert.strictEqual(Object.keys(options).length, 1);
assert.strictEqual(options.host, null);
});
it('set to undefined', function () {
var options = unifyOptions(undefined, undefined);
assert.strictEqual(Object.keys(options).length, 1);
assert.strictEqual(options.host, undefined);
});
});
describe('only an options object is passed', function () {
it('with options', function () {
var options = unifyOptions({
option: true
});
assert.strictEqual(Object.keys(options).length, 2);
assert.strictEqual(options.host, undefined);
assert.strictEqual(options.option, true);
});
it('without options', function () {
var options = unifyOptions({});
assert.strictEqual(Object.keys(options).length, 1);
assert.strictEqual(options.host, undefined);
});
it('should throw with more parameters', function () {
try {
unifyOptions({
option: true
}, undefined);
throw new Error('failed');
} catch (err) {
assert.strictEqual(err.message, 'Too many arguments passed to createClient. Please only pass the options object');
}
});
it('including url as option', function () {
var options = unifyOptions({
option: [1, 2, 3],
url: '//hm:abc@localhost:123/3'
});
assert.strictEqual(Object.keys(options).length, 6);
assert.strictEqual(options.option.length, 3);
assert.strictEqual(options.host, 'localhost');
assert.strictEqual(options.port, '123');
assert.strictEqual(options.db, '3');
assert.strictEqual(options.url, '//hm:abc@localhost:123/3');
assert.strictEqual(options.password, 'abc');
});
});
describe('faulty data', function () {
it('throws on strange connection info', function () {
try {
unifyOptions(true);
throw new Error('failed');
} catch (err) {
assert.equal(err.message, 'Unknown type of connection in createClient()');
}
});
});
});
node-redis-3.0.2/test/utils.spec.js 0000664 0000000 0000000 00000015375 13620024551 0017164 0 ustar 00root root 0000000 0000000 'use strict';
var assert = require('assert');
var Queue = require('denque');
var utils = require('../lib/utils');
var intercept = require('intercept-stdout');
describe('utils.js', function () {
describe('clone', function () {
it('ignore the object prototype and clone a nested array / object', function () {
var obj = {
a: [null, 'foo', ['bar'], {
"i'm special": true
}],
number: 5,
fn: function noop () {}
};
var clone = utils.clone(obj);
assert.deepEqual(clone, obj);
assert.strictEqual(obj.fn, clone.fn);
assert(typeof clone.fn === 'function');
});
it('replace falsy values with an empty object as return value', function () {
var a = utils.clone();
var b = utils.clone(null);
assert.strictEqual(Object.keys(a).length, 0);
assert.strictEqual(Object.keys(b).length, 0);
});
it('transform camelCase options to snake_case and add the camel_case option', function () {
var a = utils.clone({
optionOneTwo: true,
retryStrategy: false,
nested: {
onlyContainCamelCaseOnce: true
},
tls: {
rejectUnauthorized: true
}
});
assert.strictEqual(Object.keys(a).length, 5);
assert.strictEqual(a.option_one_two, true);
assert.strictEqual(a.retry_strategy, false);
assert.strictEqual(a.camel_case, true);
assert.strictEqual(a.tls.rejectUnauthorized, true);
assert.strictEqual(Object.keys(a.nested).length, 1);
});
it('throws on circular data', function () {
try {
var a = {};
a.b = a;
utils.clone(a);
throw new Error('failed');
} catch (e) {
assert(e.message !== 'failed');
}
});
});
describe('print helper', function () {
it('callback with reply', function () {
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
return '';
});
utils.print(null, 'abc');
unhookIntercept();
assert.strictEqual(text, 'Reply: abc\n');
});
it('callback with error', function () {
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
return '';
});
utils.print(new Error('Wonderful exception'));
unhookIntercept();
assert.strictEqual(text, 'Error: Wonderful exception\n');
});
});
describe('reply_in_order', function () {
var err_count = 0;
var res_count = 0;
var emitted = false;
var clientMock = {
emit: function () { emitted = true; },
offline_queue: new Queue(),
command_queue: new Queue()
};
var create_command_obj = function () {
return {
callback: function (err, res) {
if (err) err_count++;
else res_count++;
}
};
};
beforeEach(function () {
clientMock.offline_queue.clear();
clientMock.command_queue.clear();
err_count = 0;
res_count = 0;
emitted = false;
});
it('no elements in either queue. Reply in the next tick with callback', function (done) {
var called = false;
utils.reply_in_order(clientMock, function () {
called = true;
done();
}, null, null);
assert(!called);
});
it('no elements in either queue. Reply in the next tick without callback', function (done) {
assert(!emitted);
utils.reply_in_order(clientMock, null, new Error('tada'));
assert(!emitted);
setTimeout(function () {
assert(emitted);
done();
}, 1);
});
it('elements in the offline queue. Reply after the offline queue is empty and respect the command_obj callback', function (done) {
clientMock.offline_queue.push(create_command_obj());
clientMock.offline_queue.push(create_command_obj());
utils.reply_in_order(clientMock, function () {
assert.strictEqual(clientMock.offline_queue.length, 0);
assert.strictEqual(res_count, 2);
done();
}, null, null);
while (clientMock.offline_queue.length) {
clientMock.offline_queue.shift().callback(null, 'foo');
}
});
it('elements in the offline queue. Reply after the offline queue is empty and respect the command_obj error emit', function (done) {
clientMock.command_queue.push({});
clientMock.command_queue.push(create_command_obj());
clientMock.command_queue.push({});
utils.reply_in_order(clientMock, function () {
assert.strictEqual(clientMock.command_queue.length, 0);
assert(emitted);
assert.strictEqual(err_count, 1);
assert.strictEqual(res_count, 0);
done();
}, null, null);
while (clientMock.command_queue.length) {
var command_obj = clientMock.command_queue.shift();
if (command_obj.callback) {
command_obj.callback(new Error('tada'));
}
}
});
it('elements in the offline queue and the command_queue. Reply all other commands got handled respect the command_obj', function (done) {
clientMock.command_queue.push(create_command_obj());
clientMock.command_queue.push(create_command_obj());
clientMock.command_queue.push(create_command_obj());
clientMock.offline_queue.push({});
utils.reply_in_order(clientMock, function (err, res) {
assert.strictEqual(clientMock.command_queue.length, 0);
assert.strictEqual(clientMock.offline_queue.length, 0);
assert(!emitted);
assert.strictEqual(res_count, 3);
done();
}, null, null);
while (clientMock.offline_queue.length) {
clientMock.command_queue.push(clientMock.offline_queue.shift());
}
while (clientMock.command_queue.length) {
clientMock.command_queue.shift().callback(null, 'hello world');
}
});
});
});