├── .github ├── dependabot.yml └── workflows │ ├── lint.yml │ └── tests.yml ├── .gitignore ├── .mailmap ├── .overcommit.yml ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── CHANGELOG.md ├── Gemfile ├── LICENSE.md ├── README.md ├── Rakefile ├── lib ├── mock_redis.rb └── mock_redis │ ├── assertions.rb │ ├── connection_method.rb │ ├── database.rb │ ├── error.rb │ ├── exceptions.rb │ ├── expire_wrapper.rb │ ├── future.rb │ ├── geospatial_methods.rb │ ├── hash_methods.rb │ ├── indifferent_hash.rb │ ├── info_method.rb │ ├── list_methods.rb │ ├── memory_method.rb │ ├── multi_db_wrapper.rb │ ├── pipelined_wrapper.rb │ ├── set_methods.rb │ ├── sort_method.rb │ ├── stream.rb │ ├── stream │ └── id.rb │ ├── stream_methods.rb │ ├── string_methods.rb │ ├── transaction_wrapper.rb │ ├── undef_redis_methods.rb │ ├── utility_methods.rb │ ├── version.rb │ ├── zset.rb │ └── zset_methods.rb ├── mock_redis.gemspec └── spec ├── client_spec.rb ├── cloning_spec.rb ├── commands ├── append_spec.rb ├── auth_spec.rb ├── bgrewriteaof_spec.rb ├── bgsave_spec.rb ├── bitcount_spec.rb ├── bitfield_spec.rb ├── blmove_spec.rb ├── blpop_spec.rb ├── brpop_spec.rb ├── brpoplpush_spec.rb ├── connected_spec.rb ├── connection_spec.rb ├── dbsize_spec.rb ├── decr_spec.rb ├── decrby_spec.rb ├── del_spec.rb ├── disconnect_spec.rb ├── dump_spec.rb ├── echo_spec.rb ├── eval_spec.rb ├── evalsha_spec.rb ├── exists_spec.rb ├── expire_spec.rb ├── expireat_spec.rb ├── flushall_spec.rb ├── flushdb_spec.rb ├── future_spec.rb ├── geoadd_spec.rb ├── geodist_spec.rb ├── geohash_spec.rb ├── geopos_spec.rb ├── get_spec.rb ├── getbit_spec.rb ├── getdel.rb ├── getrange_spec.rb ├── getset_spec.rb ├── hdel_spec.rb ├── hexists_spec.rb ├── hget_spec.rb ├── hgetall_spec.rb ├── hincrby_spec.rb ├── hincrbyfloat_spec.rb ├── hkeys_spec.rb ├── hlen_spec.rb ├── hmget_spec.rb ├── hmset_spec.rb ├── hscan_each_spec.rb ├── hscan_spec.rb ├── hset_spec.rb ├── hsetnx_spec.rb ├── hvals_spec.rb ├── incr_spec.rb ├── incrby_spec.rb ├── incrbyfloat_spec.rb ├── info_spec.rb ├── keys_spec.rb ├── lastsave_spec.rb ├── lindex_spec.rb ├── linsert_spec.rb ├── llen_spec.rb ├── lmove_spec.rb ├── lmpop_spec.rb ├── lpop_spec.rb ├── lpush_spec.rb ├── lpushx_spec.rb ├── lrange_spec.rb ├── lrem_spec.rb ├── lset_spec.rb ├── ltrim_spec.rb ├── mapped_hmget_spec.rb ├── mapped_hmset_spec.rb ├── mapped_mget_spec.rb ├── mapped_mset_spec.rb ├── mapped_msetnx_spec.rb ├── memory_spec.rb ├── mget_spec.rb ├── move_spec.rb ├── mset_spec.rb ├── msetnx_spec.rb ├── persist_spec.rb ├── pexpire_spec.rb ├── pexpireat_spec.rb ├── ping_spec.rb ├── pipelined_spec.rb ├── psetex_spec.rb ├── pttl_spec.rb ├── quit_spec.rb ├── randomkey_spec.rb ├── rename_spec.rb ├── renamenx_spec.rb ├── restore_spec.rb ├── rpop_spec.rb ├── rpoplpush_spec.rb ├── rpush_spec.rb ├── rpushx_spec.rb ├── sadd_spec.rb ├── save_spec.rb ├── scan_each_spec.rb ├── scan_spec.rb ├── scard_spec.rb ├── script_spec.rb ├── sdiff_spec.rb ├── sdiffstore_spec.rb ├── select_spec.rb ├── set_spec.rb ├── setbit_spec.rb ├── setex_spec.rb ├── setnx_spec.rb ├── setrange_spec.rb ├── sinter_spec.rb ├── sinterstore_spec.rb ├── sismember_spec.rb ├── smembers_spec.rb ├── smismember_spec.rb ├── smove_spec.rb ├── sort_list_spec.rb ├── sort_set_spec.rb ├── sort_zset_spec.rb ├── spop_spec.rb ├── srandmember_spec.rb ├── srem_spec.rb ├── sscan_each_spec.rb ├── sscan_spec.rb ├── strlen_spec.rb ├── sunion_spec.rb ├── sunionstore_spec.rb ├── ttl_spec.rb ├── type_spec.rb ├── unwatch_spec.rb ├── watch_spec.rb ├── xadd_spec.rb ├── xlen_spec.rb ├── xrange_spec.rb ├── xread_spec.rb ├── xrevrange_spec.rb ├── xtrim_spec.rb ├── zadd_spec.rb ├── zcard_spec.rb ├── zcount_spec.rb ├── zincrby_spec.rb ├── zinterstore_spec.rb ├── zmscore_spec.rb ├── zpopmax_spec.rb ├── zpopmin_spec.rb ├── zrange_spec.rb ├── zrangebyscore_spec.rb ├── zrank_spec.rb ├── zrem_spec.rb ├── zremrangebyrank_spec.rb ├── zremrangebyscore_spec.rb ├── zrevrange_spec.rb ├── zrevrangebyscore_spec.rb ├── zrevrank_spec.rb ├── zscan_each_spec.rb ├── zscan_spec.rb ├── zscore_spec.rb └── zunionstore_spec.rb ├── mock_redis_spec.rb ├── spec_helper.rb ├── support ├── redis_multiplexer.rb └── shared_examples │ ├── does_not_cleanup_empty_strings.rb │ ├── only_operates_on_hashes.rb │ ├── only_operates_on_lists.rb │ ├── only_operates_on_sets.rb │ ├── only_operates_on_strings.rb │ ├── only_operates_on_zsets.rb │ ├── raises_on_invalid_expire_command_options.rb │ └── sorts_enumerables.rb └── transactions_spec.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | overcommit: 10 | timeout-minutes: 10 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: 3.0 20 | 21 | - name: Install dependencies 22 | run: bundle install 23 | 24 | - name: Prepare environment 25 | run: | 26 | git config --local user.email "gh-actions@example.com" 27 | git config --local user.name "GitHub Actions" 28 | bundle exec overcommit --sign 29 | 30 | - name: Run pre-commit checks 31 | run: bundle exec overcommit --run 32 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | rspec: 10 | timeout-minutes: 10 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | ruby-version: 16 | - '3.0' 17 | - '3.1' 18 | - '3.2' 19 | - '3.3' 20 | redis-version: 21 | - '6.2' 22 | - '7.0' 23 | 24 | services: 25 | redis: 26 | image: redis:${{ matrix.redis-version }}-alpine 27 | options: >- 28 | --health-cmd "redis-cli ping" 29 | --health-interval 10s 30 | --health-timeout 5s 31 | --health-retries 5 32 | ports: 33 | - 6379:6379 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - name: Set up Ruby ${{ matrix.ruby-version }} 39 | uses: ruby/setup-ruby@v1 40 | with: 41 | ruby-version: ${{ matrix.ruby-version }} 42 | bundler-cache: true 43 | 44 | - name: Run tests 45 | run: bundle exec rspec --tag redis:${{ matrix.redis-version }} 46 | 47 | - name: Code coverage reporting 48 | uses: coverallsapp/github-action@master 49 | with: 50 | github-token: ${{ secrets.github_token }} 51 | flag-name: ruby${{ matrix.ruby-version }}-${{ matrix.redis-version }} 52 | parallel: true 53 | 54 | finish: 55 | needs: rspec 56 | runs-on: ubuntu-latest 57 | 58 | steps: 59 | - name: Finalize code coverage report 60 | uses: coverallsapp/github-action@master 61 | with: 62 | github-token: ${{ secrets.github_token }} 63 | parallel-finished: true 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | pkg/* 3 | .bundle 4 | Gemfile.lock 5 | .rvmrc 6 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Samuel Merritt Sam Merritt 2 | Samuel Merritt Samuel Merritt 3 | Shane da Silva Shane da Silva 4 | Shane da Silva Shane da Silva 5 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | gemfile: Gemfile 2 | 3 | PreCommit: 4 | BundleCheck: 5 | enabled: true 6 | 7 | ExecutePermissions: 8 | enabled: true 9 | 10 | HardTabs: 11 | enabled: true 12 | 13 | RuboCop: 14 | enabled: true 15 | command: ['bundle', 'exec', 'rubocop'] 16 | 17 | TrailingWhitespace: 18 | enabled: true 19 | 20 | YamlSyntax: 21 | enabled: true 22 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | NewCops: disable 5 | SuggestExtensions: false 6 | TargetRubyVersion: '3.0' 7 | 8 | Layout/ArgumentAlignment: 9 | Enabled: false 10 | 11 | Layout/ParameterAlignment: 12 | Enabled: false 13 | 14 | Layout/DotPosition: 15 | Enabled: false 16 | 17 | Layout/EmptyLineAfterGuardClause: 18 | Enabled: false 19 | 20 | Layout/LineLength: 21 | Max: 100 22 | 23 | Lint/AssignmentInCondition: 24 | Enabled: false 25 | 26 | Metrics/AbcSize: 27 | Enabled: false 28 | 29 | Metrics/BlockLength: 30 | Enabled: false 31 | 32 | Metrics/CyclomaticComplexity: 33 | Enabled: false 34 | 35 | Metrics/ClassLength: 36 | Enabled: false 37 | 38 | Metrics/MethodLength: 39 | Enabled: false 40 | 41 | Metrics/ModuleLength: 42 | Enabled: false 43 | 44 | Metrics/PerceivedComplexity: 45 | Enabled: false 46 | 47 | # This hides the has-a versus is-a relationship indicated by the method name 48 | Naming/PredicateName: 49 | Enabled: false 50 | 51 | Style/Documentation: 52 | Enabled: false 53 | 54 | Style/DoubleNegation: 55 | Enabled: false 56 | 57 | # We have too much code that relies on modifying strings 58 | Style/FrozenStringLiteralComment: 59 | Enabled: false 60 | 61 | Style/GuardClause: 62 | Enabled: false 63 | 64 | Style/HashSyntax: 65 | Enabled: false 66 | 67 | Style/IfUnlessModifier: 68 | Enabled: false 69 | 70 | Style/Lambda: 71 | Enabled: false 72 | 73 | Style/MissingRespondToMissing: 74 | Enabled: false 75 | 76 | Style/MultilineBlockChain: 77 | Enabled: false 78 | 79 | Style/NumericPredicate: 80 | Enabled: false 81 | 82 | # Prefer curly braces except for %i/%w/%W, since those return arrays. 83 | Style/PercentLiteralDelimiters: 84 | PreferredDelimiters: 85 | '%': '{}' 86 | '%i': '[]' 87 | '%q': '{}' 88 | '%Q': '{}' 89 | '%r': '{}' 90 | '%s': '()' 91 | '%w': '[]' 92 | '%W': '[]' 93 | '%x': '{}' 94 | 95 | Style/PerlBackrefs: 96 | Enabled: false 97 | 98 | Style/RescueModifier: 99 | Enabled: false 100 | 101 | Style/SymbolArray: 102 | Enabled: false 103 | 104 | Style/TrailingCommaInArguments: 105 | Enabled: false 106 | 107 | Style/TrailingCommaInArrayLiteral: 108 | Enabled: false 109 | 110 | Style/TrailingCommaInHashLiteral: 111 | Enabled: false 112 | 113 | Style/WhenThen: 114 | Enabled: false 115 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-01-29 20:18:53 UTC using RuboCop version 1.44.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 15 10 | # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. 11 | # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to 12 | Naming/MethodParameterName: 13 | Exclude: 14 | - 'lib/mock_redis/database.rb' 15 | - 'lib/mock_redis/geospatial_methods.rb' 16 | - 'lib/mock_redis/string_methods.rb' 17 | - 'lib/mock_redis/utility_methods.rb' 18 | - 'lib/mock_redis/zset_methods.rb' 19 | - 'spec/support/redis_multiplexer.rb' 20 | 21 | # Offense count: 5 22 | # Configuration parameters: AllowedMethods. 23 | # AllowedMethods: respond_to_missing? 24 | Style/OptionalBooleanParameter: 25 | Exclude: 26 | - 'lib/mock_redis.rb' 27 | - 'lib/mock_redis/expire_wrapper.rb' 28 | - 'lib/mock_redis/multi_db_wrapper.rb' 29 | - 'lib/mock_redis/pipelined_wrapper.rb' 30 | - 'lib/mock_redis/transaction_wrapper.rb' 31 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.start do 2 | add_filter '/bin/' 3 | add_filter '/spec/' 4 | end 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | # Specify your gem's dependencies in mock_redis.gemspec 4 | gemspec 5 | 6 | # Run all pre-commit hooks via Overcommit during CI runs 7 | gem 'overcommit', '0.62.0' 8 | 9 | # Pin tool versions (which are executed by Overcommit) for CI builds 10 | gem 'rubocop', '1.44.1' 11 | 12 | gem 'base64', '~> 0.2.0' 13 | gem 'simplecov', '~> 0.22.0' 14 | gem 'simplecov-lcov', '~> 0.8.0' 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MockRedis released under the MIT license. 2 | 3 | > Copyright (c) Shane da Silva. http://shane.io 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | > SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rubocop/rake_task' 3 | require 'rspec/core/rake_task' 4 | 5 | Bundler::GemHelper.install_tasks 6 | RuboCop::RakeTask.new 7 | RSpec::Core::RakeTask.new 8 | -------------------------------------------------------------------------------- /lib/mock_redis/assertions.rb: -------------------------------------------------------------------------------- 1 | require 'redis-client' 2 | require 'mock_redis/error' 3 | 4 | class MockRedis 5 | DUMP_TYPES = RedisClient::RESP3::DUMP_TYPES 6 | 7 | module Assertions 8 | private 9 | 10 | def assert_has_args(args, command) 11 | if args.empty? 12 | raise Error.command_error( 13 | "ERR wrong number of arguments for '#{command}' command", 14 | self 15 | ) 16 | end 17 | end 18 | 19 | def assert_type(*args) 20 | args.each do |arg| 21 | DUMP_TYPES.fetch(arg.class) do |unexpected_class| 22 | unless DUMP_TYPES.keys.find { |t| t > unexpected_class } 23 | raise TypeError, "Unsupported command argument type: #{unexpected_class}" 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/mock_redis/connection_method.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | module ConnectionMethod 3 | def connection 4 | { 5 | :host => @base.host, 6 | :port => @base.port, 7 | :db => @base.db, 8 | :id => @base.id, 9 | :location => "#{@base.host}:#{@base.port}" 10 | } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mock_redis/error.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | module Error 3 | module_function 4 | 5 | def build(error_class, message, database) 6 | connection = database.connection 7 | url = "redis://#{connection[:host]}:#{connection[:port]}" 8 | error_class.new("#{message} (#{url})") 9 | end 10 | 11 | def wrong_type_error(database) 12 | build( 13 | Redis::WrongTypeError, 14 | 'WRONGTYPE Operation against a key holding the wrong kind of value', 15 | database 16 | ) 17 | end 18 | 19 | def syntax_error(database) 20 | command_error('ERR syntax error', database) 21 | end 22 | 23 | def command_error(message, database) 24 | build(Redis::CommandError, message, database) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/mock_redis/exceptions.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | WouldBlock = Class.new(StandardError) 3 | end 4 | -------------------------------------------------------------------------------- /lib/mock_redis/expire_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'mock_redis/undef_redis_methods' 2 | 3 | class MockRedis 4 | class ExpireWrapper 5 | include UndefRedisMethods 6 | 7 | def respond_to?(method, include_private = false) 8 | super || @db.respond_to?(method) 9 | end 10 | 11 | def initialize(db) 12 | @db = db 13 | end 14 | 15 | ruby2_keywords def method_missing(method, *args, &block) 16 | @db.expire_keys 17 | @db.send(method, *args, &block) 18 | end 19 | 20 | def initialize_copy(source) 21 | super 22 | @db = @db.clone 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/mock_redis/future.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | class FutureNotReady < RuntimeError; end 3 | 4 | class Future 5 | attr_reader :command, :block 6 | 7 | def initialize(command, block = nil) 8 | @command = command 9 | @block = block 10 | @result_set = false 11 | end 12 | 13 | def value 14 | raise FutureNotReady unless @result_set 15 | @result 16 | end 17 | 18 | def store_result(result) 19 | @result_set = true 20 | @result = @block ? @block.call(result) : result 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/mock_redis/indifferent_hash.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | class IndifferentHash < Hash 3 | def has_key?(key) 4 | super(key.to_s) 5 | end 6 | 7 | def key?(key) 8 | super(key.to_s) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mock_redis/memory_method.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | module MemoryMethod 3 | def memory(usage, key = nil, *_options) 4 | raise ArgumentError, "unhandled command `memory #{usage}`" if usage != 'usage' 5 | 6 | return nil unless @data.key?(key) 7 | 8 | 160 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mock_redis/multi_db_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'mock_redis/undef_redis_methods' 2 | 3 | class MockRedis 4 | class MultiDbWrapper 5 | include UndefRedisMethods 6 | 7 | def initialize(db) 8 | @db_index = 0 9 | 10 | @prototype_db = db.clone 11 | 12 | @databases = Hash.new { |h, k| h[k] = @prototype_db.clone } 13 | @databases[@db_index] = db 14 | end 15 | 16 | def respond_to?(method, include_private = false) 17 | super || current_db.respond_to?(method, include_private) 18 | end 19 | 20 | ruby2_keywords def method_missing(method, *args, &block) 21 | current_db.send(method, *args, &block) 22 | end 23 | 24 | def initialize_copy(source) 25 | super 26 | @databases = @databases.clone 27 | @databases.each_key do |k| 28 | @databases[k] = @databases[k].clone 29 | end 30 | end 31 | 32 | # Redis commands 33 | def flushall 34 | @databases.each_value(&:flushdb) 35 | 'OK' 36 | end 37 | 38 | def move(key, db_index) 39 | src = current_db 40 | dest = db(db_index) 41 | 42 | if !src.exists?(key) || dest.exists?(key) 43 | false 44 | else 45 | case current_db.type(key) 46 | when 'hash' 47 | dest.hmset(key, *src.hgetall(key).map { |k, v| [k, v] }.flatten) 48 | when 'list' 49 | while value = src.rpop(key) 50 | dest.lpush(key, value) 51 | end 52 | when 'set' 53 | while value = src.spop(key) 54 | dest.sadd(key, value) 55 | end 56 | when 'string' 57 | dest.set(key, src.get(key)) 58 | when 'zset' 59 | src.zrange(key, 0, -1, :with_scores => true).each do |(m, s)| 60 | dest.zadd(key, s, m) 61 | end 62 | else 63 | raise ArgumentError, 64 | "Can't move a key of type #{current_db.type(key).inspect}" 65 | end 66 | 67 | src.del(key) 68 | true 69 | end 70 | end 71 | 72 | def select(db_index) 73 | @db_index = db_index.to_i 74 | 'OK' 75 | end 76 | 77 | private 78 | 79 | def current_db 80 | @databases[@db_index] 81 | end 82 | 83 | def db(index) 84 | @databases[index] 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/mock_redis/pipelined_wrapper.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | class PipelinedWrapper 3 | include UndefRedisMethods 4 | 5 | def respond_to?(method, include_private = false) 6 | super || @db.respond_to?(method) 7 | end 8 | 9 | def initialize(db) 10 | @db = db 11 | @pipelined_futures = [] 12 | @nesting_level = 0 13 | end 14 | 15 | def initialize_copy(source) 16 | super 17 | @db = @db.clone 18 | @pipelined_futures = @pipelined_futures.clone 19 | end 20 | 21 | ruby2_keywords def method_missing(method, *args, &block) 22 | if in_pipeline? 23 | future = MockRedis::Future.new([method, *args], block) 24 | @pipelined_futures << future 25 | future 26 | else 27 | @db.send(method, *args, &block) 28 | end 29 | end 30 | 31 | def pipelined(_options = {}) 32 | begin 33 | @nesting_level += 1 34 | yield self 35 | ensure 36 | @nesting_level -= 1 37 | end 38 | 39 | if in_pipeline? 40 | return 41 | end 42 | 43 | responses = @pipelined_futures.flat_map do |future| 44 | result = if future.block 45 | send(*future.command, &future.block) 46 | else 47 | send(*future.command) 48 | end 49 | future.store_result(result) 50 | 51 | if future.block 52 | result 53 | else 54 | [result] 55 | end 56 | rescue StandardError => e 57 | e 58 | end 59 | @pipelined_futures = [] 60 | responses 61 | end 62 | 63 | private 64 | 65 | def in_pipeline? 66 | @nesting_level > 0 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/mock_redis/sort_method.rb: -------------------------------------------------------------------------------- 1 | require 'mock_redis/assertions' 2 | 3 | class MockRedis 4 | module SortMethod 5 | include Assertions 6 | 7 | def sort(key, options = {}) 8 | assert_type(key) 9 | 10 | enumerable = data[key] 11 | 12 | return [] if enumerable.nil? 13 | 14 | by = options[:by] 15 | limit = options[:limit] || [] 16 | store = options[:store] 17 | get_patterns = Array(options[:get]) 18 | order = options[:order] || 'ASC' 19 | direction = order.split.first 20 | 21 | projected = project(enumerable, by, get_patterns) 22 | sorted = sort_by(projected, direction) 23 | sliced = slice(sorted, limit) 24 | 25 | store ? rpush(store, sliced) : sliced 26 | end 27 | 28 | private 29 | 30 | ASCENDING_SORT = proc { |a, b| a.first <=> b.first } 31 | DESCENDING_SORT = proc { |a, b| b.first <=> a.first } 32 | 33 | def project(enumerable, by, get_patterns) 34 | enumerable.map do |*elements| 35 | element = elements.last 36 | weight = by ? lookup_from_pattern(by, element) : element 37 | value = element 38 | 39 | unless get_patterns.empty? 40 | value = get_patterns.map do |pattern| 41 | pattern == '#' ? element : lookup_from_pattern(pattern, element) 42 | end 43 | value = value.first if value.length == 1 44 | end 45 | 46 | [weight, value] 47 | end 48 | end 49 | 50 | def sort_by(projected, direction) 51 | sorter = 52 | case direction.upcase 53 | when 'DESC' 54 | DESCENDING_SORT 55 | when 'ASC', 'ALPHA' 56 | ASCENDING_SORT 57 | else 58 | raise "Invalid direction '#{direction}'" 59 | end 60 | 61 | projected.sort(&sorter).map(&:last) 62 | end 63 | 64 | def slice(sorted, limit) 65 | skip = limit.first || 0 66 | take = limit.last || sorted.length 67 | 68 | sorted[skip...(skip + take)] || sorted 69 | end 70 | 71 | def lookup_from_pattern(pattern, element) 72 | key = pattern.sub('*', element) 73 | 74 | if (hash_parts = key.split('->')).length > 1 75 | hget hash_parts.first, hash_parts.last 76 | else 77 | get key 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/mock_redis/stream.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require 'set' 3 | require 'date' 4 | require 'mock_redis/stream/id' 5 | 6 | class MockRedis 7 | class Stream 8 | include Enumerable 9 | extend Forwardable 10 | 11 | attr_accessor :members 12 | 13 | def_delegators :members, :empty? 14 | 15 | def initialize 16 | @members = Set.new 17 | @last_id = nil 18 | end 19 | 20 | def last_id 21 | @last_id.to_s 22 | end 23 | 24 | def add(id, values) 25 | @last_id = MockRedis::Stream::Id.new(id, min: @last_id) 26 | if @last_id.to_s == '0-0' 27 | raise Redis::CommandError, 28 | 'ERR The ID specified in XADD must be greater than 0-0' 29 | end 30 | members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]] 31 | @last_id.to_s 32 | end 33 | 34 | def trim(count) 35 | deleted = @members.size - count 36 | if deleted > 0 37 | @members = if count == 0 38 | Set.new 39 | else 40 | @members.to_a[-count..].to_set 41 | end 42 | deleted 43 | else 44 | 0 45 | end 46 | end 47 | 48 | def range(start, finish, reversed, *opts_in) 49 | opts = options opts_in, ['count'] 50 | start_id = MockRedis::Stream::Id.new(start) 51 | finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY) 52 | items = if start_id.exclusive 53 | members 54 | .select { |m| (start_id < m[0]) && (finish_id >= m[0]) } 55 | .map { |m| [m[0].to_s, m[1]] } 56 | else 57 | members 58 | .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) } 59 | .map { |m| [m[0].to_s, m[1]] } 60 | end 61 | items.reverse! if reversed 62 | return items.first(opts['count'].to_i) if opts.key?('count') 63 | items 64 | end 65 | 66 | def read(id, *opts_in) 67 | opts = options opts_in, %w[count block] 68 | stream_id = MockRedis::Stream::Id.new(id) 69 | items = members.select { |m| (stream_id < m[0]) }.map { |m| [m[0].to_s, m[1]] } 70 | return items.first(opts['count'].to_i) if opts.key?('count') 71 | items 72 | end 73 | 74 | def each(&block) 75 | members.each(&block) 76 | end 77 | 78 | private 79 | 80 | def options(opts_in, permitted) 81 | opts_out = {} 82 | raise Redis::CommandError, 'ERR syntax error' unless (opts_in.length % 2).zero? 83 | opts_in.each_slice(2).map { |pair| opts_out[pair[0].downcase] = pair[1] } 84 | raise Redis::CommandError, 'ERR syntax error' unless (opts_out.keys - permitted).empty? 85 | opts_out 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/mock_redis/stream/id.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | class Stream 3 | class Id 4 | include Comparable 5 | 6 | attr_accessor :timestamp, :sequence, :exclusive 7 | 8 | def initialize(id, min: nil, sequence: 0) 9 | @exclusive = false 10 | case id 11 | when '*' 12 | @timestamp = (Time.now.to_f * 1000).to_i 13 | @sequence = 0 14 | if self <= min 15 | @timestamp = min.timestamp 16 | @sequence = min.sequence + 1 17 | end 18 | when '-' 19 | @timestamp = @sequence = 0 20 | when '+' 21 | @timestamp = @sequence = Float::INFINITY 22 | else 23 | if id.is_a? String 24 | # See https://redis.io/topics/streams-intro 25 | # Ids are a unix timestamp in milliseconds followed by an 26 | # optional dash sequence number, e.g. -0. They can also optionally 27 | # be prefixed with '(' to change the XRANGE to exclusive. 28 | (_, @timestamp, @sequence) = id.match(/^\(?(\d+)-?(\d+)?$/).to_a 29 | @exclusive = true if id[0] == '(' 30 | if @timestamp.nil? 31 | raise Redis::CommandError, 32 | 'ERR Invalid stream ID specified as stream command argument' 33 | end 34 | @timestamp = @timestamp.to_i 35 | else 36 | @timestamp = id 37 | end 38 | @sequence = @sequence.nil? ? sequence : @sequence.to_i 39 | if self <= min 40 | raise Redis::CommandError, 41 | 'ERR The ID specified in XADD is equal or smaller than ' \ 42 | 'the target stream top item' 43 | end 44 | end 45 | end 46 | 47 | def to_s 48 | "#{@timestamp}-#{@sequence}" 49 | end 50 | 51 | def <=>(other) 52 | return 1 if other.nil? 53 | return @sequence <=> other.sequence if @timestamp == other.timestamp 54 | @timestamp <=> other.timestamp 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/mock_redis/transaction_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'mock_redis/undef_redis_methods' 2 | require 'mock_redis/error' 3 | 4 | class MockRedis 5 | class TransactionWrapper 6 | include UndefRedisMethods 7 | 8 | def respond_to?(method, include_private = false) 9 | super || @db.respond_to?(method) 10 | end 11 | 12 | def initialize(db) 13 | @db = db 14 | @transaction_futures = [] 15 | @multi_stack = [] 16 | end 17 | 18 | ruby2_keywords def method_missing(method, *args, &block) 19 | if in_multi? 20 | MockRedis::Future.new([method, *args], block).tap do |future| 21 | @transaction_futures << future 22 | end 23 | else 24 | @db.expire_keys 25 | @db.send(method, *args, &block) 26 | end 27 | end 28 | 29 | def initialize_copy(source) 30 | super 31 | @db = @db.clone 32 | @transaction_futures = @transaction_futures.clone 33 | @multi_stack = @multi_stack.clone 34 | end 35 | 36 | def discard 37 | unless in_multi? 38 | raise Error.command_error('ERR DISCARD without MULTI', self) 39 | end 40 | pop_multi 41 | 42 | @transaction_futures = [] 43 | 'OK' 44 | end 45 | 46 | def exec 47 | unless in_multi? 48 | raise Error.command_error('ERR EXEC without MULTI', self) 49 | end 50 | 51 | pop_multi 52 | return if in_multi? 53 | 54 | responses = @transaction_futures.map do |future| 55 | result = send(*future.command) 56 | future.store_result(result) 57 | future.value 58 | end 59 | 60 | responses 61 | ensure 62 | # At this point, multi is done, so we can't call discard anymore. 63 | # Therefore, we need to clear the transaction futures manually. 64 | @transaction_futures = [] 65 | end 66 | 67 | def in_multi? 68 | @multi_stack.any? 69 | end 70 | 71 | def push_multi 72 | @multi_stack.push(@multi_stack.size + 1) 73 | end 74 | 75 | def pop_multi 76 | @multi_stack.pop 77 | end 78 | 79 | def multi 80 | raise Redis::BaseError, "Can't nest multi transaction" if in_multi? 81 | 82 | push_multi 83 | 84 | begin 85 | yield(self) 86 | exec 87 | rescue StandardError => e 88 | discard if in_multi? 89 | raise e 90 | end 91 | end 92 | 93 | def pipelined 94 | yield(self) if block_given? 95 | end 96 | 97 | def unwatch 98 | 'OK' 99 | end 100 | 101 | def watch(*_) 102 | if block_given? 103 | yield self 104 | else 105 | 'OK' 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/mock_redis/undef_redis_methods.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | module UndefRedisMethods 3 | def self.included(klass) 4 | if klass.instance_methods.map(&:to_s).include?('type') 5 | klass.send(:undef_method, 'type') 6 | end 7 | klass.send(:undef_method, 'exec') 8 | klass.send(:undef_method, 'select') 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mock_redis/utility_methods.rb: -------------------------------------------------------------------------------- 1 | class MockRedis 2 | module UtilityMethods 3 | private 4 | 5 | def with_thing_at(key, assertion, empty_thing_generator) 6 | send(assertion, key) 7 | data[key] ||= empty_thing_generator.call 8 | data_key_ref = data[key] 9 | ret = yield data[key] 10 | data[key] = data_key_ref if data[key].nil? 11 | primitive?(ret) ? ret.dup : ret 12 | ensure 13 | clean_up_empties_at(key) 14 | end 15 | 16 | def primitive?(value) 17 | value.is_a?(::Array) || value.is_a?(::Hash) || value.is_a?(::String) 18 | end 19 | 20 | def clean_up_empties_at(key) 21 | if data[key]&.empty? && data[key] != '' && !data[key].is_a?(Stream) 22 | del(key) 23 | end 24 | end 25 | 26 | def common_scan(values, cursor, opts = {}) 27 | count = (opts[:count] || 10).to_i 28 | cursor = cursor.to_i 29 | match = opts[:match] || '*' 30 | key = opts[:key] || lambda { |x| x } 31 | type_opt = opts[:type] 32 | filtered_values = [] 33 | 34 | limit = cursor + count 35 | next_cursor = limit >= values.length ? '0' : limit.to_s 36 | 37 | unless values[cursor...limit].nil? 38 | filtered_values = values[cursor...limit].select do |val| 39 | redis_pattern_to_ruby_regex(match).match(key.call(val)) && 40 | (type_opt.nil? || type(val) == type_opt) 41 | end 42 | end 43 | 44 | [next_cursor, filtered_values] 45 | end 46 | 47 | def twos_complement_encode(n, size) 48 | if n < 0 49 | str = (n + 1).abs.to_s(2) 50 | 51 | binary = left_pad(str, size - 1).chars.map { |c| c == '0' ? 1 : 0 } 52 | binary.unshift(1) 53 | else 54 | binary = left_pad(n.abs.to_s(2), size - 1).chars.map(&:to_i) 55 | binary.unshift(0) 56 | end 57 | 58 | binary 59 | end 60 | 61 | def twos_complement_decode(array) 62 | total = 0 63 | 64 | array.each.with_index do |bit, index| 65 | total += 2**(array.length - index - 1) if bit == 1 66 | total = -total if index == 0 67 | end 68 | 69 | total 70 | end 71 | 72 | def left_pad(str, size) 73 | str = "0#{str}" while str.length < size 74 | 75 | str 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/mock_redis/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Defines the gem version. 4 | class MockRedis 5 | VERSION = '0.50.0' 6 | end 7 | -------------------------------------------------------------------------------- /mock_redis.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path('lib', __dir__) 2 | require 'mock_redis/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'mock_redis' 6 | s.version = MockRedis::VERSION 7 | s.license = 'MIT' 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Shane da Silva', 'Samuel Merritt'] 10 | s.email = ['shane@dasilva.io'] 11 | s.homepage = 'https://github.com/sds/mock_redis' 12 | s.summary = 'Redis mock that just lives in memory; useful for testing.' 13 | 14 | s.description = <<-MSG.strip.gsub(/\s+/, ' ') 15 | Instantiate one with `redis = MockRedis.new` and treat it like you would a 16 | normal Redis object. It supports all the usual Redis operations. 17 | MSG 18 | 19 | s.files = Dir.chdir(__dir__) do 20 | `git ls-files -z`.split("\x0").select do |file| 21 | file.start_with?('lib') || file.end_with?('.md') 22 | end 23 | end 24 | 25 | s.metadata = { 26 | 'bug_tracker_uri' => "#{s.homepage}/issues", 27 | 'changelog_uri' => "#{s.homepage}/blob/v#{s.version}/CHANGELOG.md", 28 | 'documentation_uri' => "https://www.rubydoc.info/gems/#{s.name}/#{s.version}", 29 | 'homepage_uri' => s.homepage, 30 | 'source_code_uri' => "#{s.homepage}/tree/v#{s.version}", 31 | } 32 | 33 | s.require_paths = ['lib'] 34 | 35 | s.required_ruby_version = '>= 3.0' 36 | 37 | s.add_runtime_dependency 'redis', '~> 5' 38 | 39 | s.add_development_dependency 'rake', '~> 13' 40 | s.add_development_dependency 'rspec', '~> 3.0' 41 | s.add_development_dependency 'rspec-its', '~> 1.0' 42 | s.add_development_dependency 'timecop', '~> 0.9.1' 43 | end 44 | -------------------------------------------------------------------------------- /spec/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'client' do 4 | context '#reconnect' do 5 | it 'reconnects' do 6 | redis = MockRedis.new 7 | expect(redis.reconnect).to eq(redis) 8 | end 9 | end 10 | 11 | context '#connect' do 12 | it 'connects' do 13 | redis = MockRedis.new 14 | expect(redis.connect).to eq(redis) 15 | end 16 | end 17 | 18 | context '#disconnect!' do 19 | it 'responds to disconnect!' do 20 | expect(MockRedis.new).to respond_to(:disconnect!) 21 | end 22 | end 23 | 24 | context '#close' do 25 | it 'responds to close' do 26 | expect(MockRedis.new).to respond_to(:close) 27 | end 28 | end 29 | 30 | context '#with' do 31 | it 'supports with' do 32 | redis = MockRedis.new 33 | expect(redis.with { |c| c.set('key', 'value') }).to eq('OK') 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/cloning_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'MockRedis#clone' do 4 | before do 5 | @mock = MockRedis.new 6 | end 7 | 8 | context 'the stored data' do 9 | before do 10 | @mock.set('foo', 'bar') 11 | @mock.hset('foohash', 'bar', 'baz') 12 | @mock.lpush('foolist', 'bar') 13 | @mock.sadd('fooset', 'bar') 14 | @mock.zadd('foozset', 1, 'bar') 15 | 16 | @clone = @mock.clone 17 | end 18 | 19 | it 'copies the stored data to the clone' do 20 | expect(@clone.get('foo')).to eq('bar') 21 | end 22 | 23 | it 'performs a deep copy (string values)' do 24 | @mock.del('foo') 25 | expect(@clone.get('foo')).to eq('bar') 26 | end 27 | 28 | it 'performs a deep copy (list values)' do 29 | @mock.lpop('foolist') 30 | expect(@clone.lrange('foolist', 0, 1)).to eq(['bar']) 31 | end 32 | 33 | it 'performs a deep copy (hash values)' do 34 | @mock.hset('foohash', 'bar', 'quux') 35 | expect(@clone.hgetall('foohash')).to eq({ 'bar' => 'baz' }) 36 | end 37 | 38 | it 'performs a deep copy (set values)' do 39 | @mock.srem('fooset', 'bar') 40 | expect(@clone.smembers('fooset')).to eq(['bar']) 41 | end 42 | 43 | it 'performs a deep copy (zset values)' do 44 | @mock.zadd('foozset', 2, 'bar') 45 | expect(@clone.zscore('foozset', 'bar')).to eq(1.0) 46 | end 47 | end 48 | 49 | context 'expiration times' do 50 | before do 51 | @mock.set('foo', 1) 52 | @mock.expire('foo', 60_026) 53 | 54 | @clone = @mock.clone 55 | end 56 | 57 | it 'copies the expiration times' do 58 | expect(@clone.ttl('foo')).to be > 0 59 | end 60 | 61 | it 'deep-copies the expiration times' do 62 | @mock.persist('foo') 63 | expect(@clone.ttl('foo')).to be > 0 64 | end 65 | 66 | it 'deep-copies the expiration times' do 67 | @clone.persist('foo') 68 | expect(@mock.ttl('foo')).to be > 0 69 | end 70 | end 71 | 72 | context 'transactional info' do 73 | def test_clone 74 | @mock.multi do |r| 75 | r.incr('foo') 76 | r.incrby('foo', 2) 77 | r.incrby('foo', 4) 78 | 79 | yield r.clone 80 | end 81 | end 82 | 83 | it 'makes sure the clone is in a transaction' do 84 | expect { test_clone(&:exec) }.not_to raise_error 85 | end 86 | 87 | it 'deep-copies the queued commands' do 88 | result = test_clone do |clone| 89 | clone.incrby('foo', 8) 90 | expect(clone.exec).to eq([1, 3, 7, 15]) 91 | end 92 | 93 | expect(result).to eq([1, 3, 7]) 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/commands/append_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#append(key, value)' do 4 | before { @key = 'mock-redis-test:append' } 5 | 6 | it 'returns the new length of the string' do 7 | @redises.set(@key, 'porkchop') 8 | expect(@redises.append(@key, 'sandwiches')).to eq(18) 9 | end 10 | 11 | it 'appends value to the previously-stored value' do 12 | @redises.set(@key, 'porkchop') 13 | @redises.append(@key, 'sandwiches') 14 | 15 | expect(@redises.get(@key)).to eq('porkchopsandwiches') 16 | end 17 | 18 | it 'treats a missing key as an empty string' do 19 | @redises.append(@key, 'foo') 20 | expect(@redises.get(@key)).to eq('foo') 21 | end 22 | 23 | it_should_behave_like 'a string-only command' 24 | end 25 | -------------------------------------------------------------------------------- /spec/commands/auth_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#auth(password) [mock only]' do 4 | it "just returns 'OK'" do 5 | expect(@redises.mock.auth('foo')).to eq('OK') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/bgrewriteaof_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#bgrewriteaof [mock only]' do 4 | it 'just returns a canned string' do 5 | expect(@redises.mock.bgrewriteaof).to match(/append/) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/bgsave_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#bgsave [mock only]' do 4 | it 'just returns a canned string' do 5 | expect(@redises.mock.bgsave).to match(/saving/) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/bitcount_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#bitcount(key [, start, end ])' do 4 | before do 5 | @key = 'mock-redis-test:bitcount' 6 | @redises.set(@key, 'foobar') 7 | end 8 | 9 | it 'gets the number of set bits from the key' do 10 | expect(@redises.bitcount(@key)).to eq(26) 11 | end 12 | 13 | it 'gets the number of set bits from the key in an interval' do 14 | expect(@redises.bitcount(@key, 0, 1000)).to eq(26) 15 | expect(@redises.bitcount(@key, 0, 0)).to eq(4) 16 | expect(@redises.bitcount(@key, 1, 1)).to eq(6) 17 | expect(@redises.bitcount(@key, 1, -2)).to eq(18) 18 | end 19 | 20 | it 'treats nonexistent keys as empty strings' do 21 | expect(@redises.bitcount('mock-redis-test:not-found')).to eq(0) 22 | end 23 | 24 | it_should_behave_like 'a string-only command' 25 | end 26 | -------------------------------------------------------------------------------- /spec/commands/blmove_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#blmove(source, destination, wherefrom, whereto, timeout)' do 4 | before do 5 | @list1 = 'mock-redis-test:blmove-list1' 6 | @list2 = 'mock-redis-test:blmove-list2' 7 | 8 | @redises.lpush(@list1, 'b') 9 | @redises.lpush(@list1, 'a') 10 | 11 | @redises.lpush(@list2, 'y') 12 | @redises.lpush(@list2, 'x') 13 | end 14 | 15 | it 'returns the value moved' do 16 | expect(@redises.blmove(@list1, @list2, 'left', 'right')).to eq('a') 17 | end 18 | 19 | it 'takes the first element of source and appends it to destination' do 20 | @redises.blmove(@list1, @list2, 'left', 'right') 21 | 22 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b]) 23 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y a]) 24 | end 25 | 26 | it 'raises an error on negative timeout' do 27 | expect do 28 | @redises.blmove(@list1, @list2, 'left', 'right', :timeout => -1) 29 | end.to raise_error(ArgumentError) 30 | end 31 | 32 | let(:default_error) { RedisMultiplexer::MismatchedResponse } 33 | it_should_behave_like 'a list-only command' 34 | 35 | context '[mock only]' do 36 | it 'ignores positive timeouts and returns nil' do 37 | expect( 38 | @redises.mock.blmove( 39 | 'mock-redis-test:not-here', 40 | @list1, 41 | 'left', 42 | 'right', 43 | :timeout => 1 44 | ) 45 | ).to be_nil 46 | end 47 | 48 | it 'ignores positive legacy timeouts and returns nil' do 49 | expect(@redises.mock.blmove('mock-redis-test:not-here', @list1, 'left', 'right', 1)). 50 | to be_nil 51 | end 52 | 53 | it 'raises WouldBlock on zero timeout (no blocking in the mock)' do 54 | expect do 55 | @redises.mock.blmove('mock-redis-test:not-here', @list1, 'left', 'right', :timeout => 0) 56 | end.to raise_error(MockRedis::WouldBlock) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/commands/blpop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#blpop(key [, key, ...,], timeout)' do 4 | before do 5 | @list1 = 'mock-redis-test:blpop1' 6 | @list2 = 'mock-redis-test:blpop2' 7 | 8 | @redises.rpush(@list1, 'one') 9 | @redises.rpush(@list1, 'two') 10 | @redises.rpush(@list2, 'ten') 11 | @redises.rpush(@list2, 'eleven') 12 | end 13 | 14 | it 'returns [first-nonempty-list, popped-value]' do 15 | expect(@redises.blpop(@list1, @list2)).to eq([@list1, 'one']) 16 | end 17 | 18 | it 'pops that value off the list' do 19 | @redises.blpop(@list1, @list2) 20 | @redises.blpop(@list1, @list2) 21 | 22 | expect(@redises.blpop(@list1, @list2)).to eq([@list2, 'ten']) 23 | end 24 | 25 | it 'ignores empty keys' do 26 | expect(@redises.blpop('mock-redis-test:not-here', @list1)).to eq( 27 | [@list1, 'one'] 28 | ) 29 | end 30 | 31 | it 'raises an error on negative timeout' do 32 | expect do 33 | @redises.blpop(@list1, @list2, :timeout => -1) 34 | end.to raise_error(ArgumentError) 35 | end 36 | 37 | it_should_behave_like 'a list-only command' 38 | 39 | context '[mock only]' do 40 | it 'ignores positive timeouts and returns nil' do 41 | expect(@redises.mock.blpop('mock-redis-test:not-here', :timeout => 1)).to be_nil 42 | end 43 | 44 | it 'ignores positive legacy timeouts and returns nil' do 45 | expect(@redises.mock.blpop('mock-redis-test:not-here', 1)).to be_nil 46 | end 47 | 48 | it 'raises WouldBlock on zero timeout (no blocking in the mock)' do 49 | expect do 50 | @redises.mock.blpop('mock-redis-test:not-here', :timeout => 0) 51 | end.to raise_error(MockRedis::WouldBlock) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/commands/brpop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#brpop(key [, key, ...,], timeout)' do 4 | before do 5 | @list1 = 'mock-redis-test:brpop1' 6 | @list2 = 'mock-redis-test:brpop2' 7 | 8 | @redises.rpush(@list1, 'one') 9 | @redises.rpush(@list1, 'two') 10 | 11 | @redises.rpush(@list2, 'ten') 12 | end 13 | 14 | it 'returns [first-nonempty-list, popped-value]' do 15 | expect(@redises.brpop(@list1, @list2)).to eq([@list1, 'two']) 16 | end 17 | 18 | it 'pops that value off the list' do 19 | @redises.brpop(@list1, @list2) 20 | @redises.brpop(@list1, @list2) 21 | expect(@redises.brpop(@list1, @list2)).to eq([@list2, 'ten']) 22 | end 23 | 24 | it 'ignores empty keys' do 25 | expect(@redises.brpop('mock-redis-test:not-here', @list1)).to eq( 26 | [@list1, 'two'] 27 | ) 28 | end 29 | 30 | # TODO: Not sure how redis-rb is handling this but they're not raising an error 31 | # it 'raises an error on subsecond timeouts' do 32 | # lambda do 33 | # @redises.brpop(@list1, @list2, :timeout => 0.5) 34 | # end.should raise_error(Redis::CommandError) 35 | # end 36 | 37 | it 'raises an error on negative timeout' do 38 | expect do 39 | @redises.brpop(@list1, @list2, :timeout => -1) 40 | end.to raise_error(ArgumentError) 41 | end 42 | 43 | it_should_behave_like 'a list-only command' 44 | 45 | context '[mock only]' do 46 | it 'ignores positive timeouts and returns nil' do 47 | expect(@redises.mock.brpop('mock-redis-test:not-here', :timeout => 1)).to be_nil 48 | end 49 | 50 | it 'ignores positive legacy timeouts and returns nil' do 51 | expect(@redises.mock.brpop('mock-redis-test:not-here', 1)).to be_nil 52 | end 53 | 54 | it 'raises WouldBlock on zero timeout (no blocking in the mock)' do 55 | expect do 56 | @redises.mock.brpop('mock-redis-test:not-here', :timeout => 0) 57 | end.to raise_error(MockRedis::WouldBlock) 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/commands/brpoplpush_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#brpoplpush(source, destination, timeout)' do 4 | before do 5 | @list1 = 'mock-redis-test:brpoplpush1' 6 | @list2 = 'mock-redis-test:brpoplpush2' 7 | 8 | @redises.rpush(@list1, 'A') 9 | @redises.rpush(@list1, 'B') 10 | 11 | @redises.rpush(@list2, 'alpha') 12 | @redises.rpush(@list2, 'beta') 13 | end 14 | 15 | it 'takes the last element of source and prepends it to destination' do 16 | @redises.brpoplpush(@list1, @list2) 17 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[A]) 18 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[B alpha beta]) 19 | end 20 | 21 | it 'returns the moved element' do 22 | expect(@redises.brpoplpush(@list1, @list2)).to eq('B') 23 | end 24 | 25 | it 'raises an error on negative timeout' do 26 | expect do 27 | @redises.brpoplpush(@list1, @list2, :timeout => -1) 28 | end.to raise_error(ArgumentError) 29 | end 30 | 31 | it_should_behave_like 'a list-only command' 32 | 33 | context '[mock only]' do 34 | it 'ignores positive timeouts and returns nil' do 35 | expect(@redises.mock.brpoplpush('mock-redis-test:not-here', @list1, :timeout => 1)). 36 | to be_nil 37 | end 38 | 39 | it 'raises error if there is extra argument' do 40 | expect do 41 | @redises.mock.brpoplpush('mock-redis-test:not-here', @list1, 1) 42 | end.to raise_error(ArgumentError) 43 | end 44 | 45 | it 'raises WouldBlock on zero timeout (no blocking in the mock)' do 46 | expect do 47 | @redises.mock.brpoplpush('mock-redis-test:not-here', @list1, :timeout => 0) 48 | end.to raise_error(MockRedis::WouldBlock) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/commands/connected_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#connected? [mock only]' do 4 | it 'returns true' do 5 | expect(@redises.mock.connected?).to eq(true) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#connection' do 4 | let(:redis) { @redises.mock } 5 | 6 | it 'returns the correct values' do 7 | expect(redis.connection).to eq( 8 | { 9 | :host => 'localhost', 10 | :port => 6379, 11 | :db => 0, 12 | :id => 'redis://localhost:6379/0', 13 | :location => 'localhost:6379' 14 | } 15 | ) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/commands/dbsize_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#dbsize [mock only]' do 4 | # mock only since we can't guarantee that the real Redis is empty 5 | before { @mock = @redises.mock } 6 | 7 | it 'returns 0 for an empty DB' do 8 | expect(@mock.dbsize).to eq(0) 9 | end 10 | 11 | it 'returns the number of keys in the DB' do 12 | @mock.set('foo', 1) 13 | @mock.lpush('bar', 2) 14 | @mock.hset('baz', 3, 4) 15 | 16 | expect(@mock.dbsize).to eq(3) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/commands/decr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#decr(key)' do 4 | before { @key = 'mock-redis-test:46895' } 5 | 6 | it 'returns the value after the decrement' do 7 | @redises.set(@key, 2) 8 | expect(@redises.decr(@key)).to eq(1) 9 | end 10 | 11 | it 'treats a missing key like 0' do 12 | expect(@redises.decr(@key)).to eq(-1) 13 | end 14 | 15 | it 'decrements negative numbers' do 16 | @redises.set(@key, -10) 17 | expect(@redises.decr(@key)).to eq(-11) 18 | end 19 | 20 | it 'works multiple times' do 21 | expect(@redises.decr(@key)).to eq(-1) 22 | expect(@redises.decr(@key)).to eq(-2) 23 | expect(@redises.decr(@key)).to eq(-3) 24 | end 25 | 26 | it 'raises an error if the value does not look like an integer' do 27 | @redises.set(@key, 'minus one') 28 | expect do 29 | @redises.decr(@key) 30 | end.to raise_error(Redis::CommandError) 31 | end 32 | 33 | it_should_behave_like 'a string-only command' 34 | end 35 | -------------------------------------------------------------------------------- /spec/commands/decrby_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#decrby(key, decrement)' do 4 | before { @key = 'mock-redis-test:43650' } 5 | 6 | it 'returns the value after the decrement' do 7 | @redises.set(@key, 4) 8 | expect(@redises.decrby(@key, 2)).to eq(2) 9 | end 10 | 11 | it 'treats a missing key like 0' do 12 | expect(@redises.decrby(@key, 2)).to eq(-2) 13 | end 14 | 15 | it 'decrements negative numbers' do 16 | @redises.set(@key, -10) 17 | expect(@redises.decrby(@key, 2)).to eq(-12) 18 | end 19 | 20 | it 'works multiple times' do 21 | expect(@redises.decrby(@key, 2)).to eq(-2) 22 | expect(@redises.decrby(@key, 2)).to eq(-4) 23 | expect(@redises.decrby(@key, 2)).to eq(-6) 24 | end 25 | 26 | it 'raises an error if the value does not look like an integer' do 27 | @redises.set(@key, 'one') 28 | expect do 29 | @redises.decrby(@key, 1) 30 | end.to raise_error(Redis::CommandError) 31 | end 32 | 33 | it_should_behave_like 'a string-only command' 34 | end 35 | -------------------------------------------------------------------------------- /spec/commands/del_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#del(key [, key, ...])' do 4 | before :all do 5 | sleep 1 - (Time.now.to_f % 1) 6 | end 7 | 8 | before :each do 9 | # TODO: Redis appears to be returning a timestamp a few seconds in the future 10 | # so we're ignoring the last 5 digits (time in milliseconds) 11 | @redises._gsub(/\d{5}-\d/, '...-.') 12 | end 13 | 14 | it 'returns the number of keys deleted' do 15 | @redises.set('mock-redis-test:1', 1) 16 | @redises.set('mock-redis-test:2', 1) 17 | 18 | expect( 19 | @redises.del( 20 | 'mock-redis-test:1', 21 | 'mock-redis-test:2', 22 | 'mock-redis-test:other' 23 | ) 24 | ).to eq(2) 25 | end 26 | 27 | it 'actually removes the key' do 28 | @redises.set('mock-redis-test:1', 1) 29 | @redises.del('mock-redis-test:1') 30 | 31 | expect(@redises.get('mock-redis-test:1')).to be_nil 32 | end 33 | 34 | it 'accepts an array of keys' do 35 | @redises.set('mock-redis-test:1', 1) 36 | @redises.set('mock-redis-test:2', 2) 37 | 38 | @redises.del(%w[mock-redis-test:1 mock-redis-test:2]) 39 | 40 | expect(@redises.get('mock-redis-test:1')).to be_nil 41 | expect(@redises.get('mock-redis-test:2')).to be_nil 42 | end 43 | 44 | it 'raises an error if an empty array is given' do 45 | expect { @redises.del [] }.not_to raise_error 46 | end 47 | 48 | it 'removes a stream key' do 49 | @redises.xadd('mock-redis-stream', { key: 'value' }, maxlen: 0) 50 | expect(@redises.exists?('mock-redis-stream')).to eq true 51 | @redises.del('mock-redis-stream') 52 | expect(@redises.exists?('mock-redis-stream')).to eq false 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/commands/disconnect_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#disconnect [mock only]' do 4 | it 'returns nil' do 5 | expect(@redises.mock.disconnect).to be_nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/dump_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#dump(key)' do 4 | before do 5 | @key = 'mock-redis-test:45794' 6 | # These are mock-only, since our dump/restore implementations 7 | # aren't compatible with real redis. 8 | @mock = @redises.mock 9 | end 10 | 11 | it 'returns nil for keys that do not exist' do 12 | expect(@mock.dump(@key)).to be_nil 13 | end 14 | 15 | it 'returns a serialized value for keys that do exist' do 16 | @mock.set(@key, '2') 17 | expect(@mock.dump(@key)).to eq(Marshal.dump('2')) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/echo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#echo(str)' do 4 | it 'returns its argument' do 5 | expect(@redises.echo('foo')).to eq('foo') 6 | end 7 | 8 | it 'stringifies its argument' do 9 | expect(@redises.echo(1)).to eq('1') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/commands/eval_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#eval(*)' do 4 | it 'returns nothing' do 5 | expect(@redises.eval('return nil')).to be_nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/evalsha_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#evalsha(*)' do 4 | let(:script) { 'return nil' } 5 | let(:script_digest) { Digest::SHA1.hexdigest(script) } 6 | 7 | it 'returns nothing' do 8 | expect(@redises.evalsha(script_digest)).to be_nil 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/commands/exists_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#exists(*keys)' do 4 | before { @key1 = 'mock-redis-test:exists1' } 5 | before { @key2 = 'mock-redis-test:exists2' } 6 | 7 | it 'returns 0 for keys that do not exist' do 8 | expect(@redises.exists(@key1)).to eq(0) 9 | expect(@redises.exists(@key1, @key2)).to eq(0) 10 | end 11 | 12 | it 'returns 1 for keys that do exist' do 13 | @redises.set(@key1, 1) 14 | expect(@redises.exists(@key1)).to eq(1) 15 | end 16 | 17 | it 'returns the count of all keys that exist' do 18 | @redises.set(@key1, 1) 19 | @redises.set(@key2, 1) 20 | expect(@redises.exists(@key1, @key2)).to eq(2) 21 | expect(@redises.exists(@key1, @key2, 'does-not-exist')).to eq(2) 22 | end 23 | end 24 | 25 | RSpec.describe '#exists?(*keys)' do 26 | before { @key1 = 'mock-redis-test:exists1' } 27 | before { @key2 = 'mock-redis-test:exists2' } 28 | 29 | it 'returns false for keys that do not exist' do 30 | expect(@redises.exists?(@key1)).to eq(false) 31 | expect(@redises.exists?(@key1, @key2)).to eq(false) 32 | end 33 | 34 | it 'returns true for keys that do exist' do 35 | @redises.set(@key1, 1) 36 | expect(@redises.exists?(@key1)).to eq(true) 37 | end 38 | 39 | it 'returns true if any keys exist' do 40 | @redises.set(@key2, 1) 41 | expect(@redises.exists?(@key1, @key2)).to eq(true) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/commands/expireat_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#expireat(key, timestamp)' do 4 | before do 5 | @key = 'mock-redis-test:expireat' 6 | @redises.set(@key, 'spork') 7 | end 8 | 9 | it 'returns true for a key that exists' do 10 | expect(@redises.expireat(@key, Time.now.to_i + 1)).to eq(true) 11 | end 12 | 13 | it 'returns false for a key that does not exist' do 14 | expect(@redises.expireat('mock-redis-test:nonesuch', Time.now.to_i + 1)).to eq(false) 15 | end 16 | 17 | it 'removes a key immediately when timestamp is now' do 18 | @redises.expireat(@key, Time.now.to_i) 19 | expect(@redises.get(@key)).to be_nil 20 | end 21 | 22 | it 'returns true when time is a Time object' do 23 | expect(@redises.expireat(@key, Time.now)).to eq true 24 | end 25 | 26 | it 'works with options', redis: '7.0' do 27 | expect(@redises.expire(@key, Time.now.to_i + 20)).to eq(true) 28 | expect(@redises.expire(@key, Time.now.to_i + 10, lt: true)).to eq(true) 29 | expect(@redises.expire(@key, Time.now.to_i + 15, lt: true)).to eq(false) 30 | expect(@redises.expire(@key, Time.now.to_i + 20, gt: true)).to eq(true) 31 | expect(@redises.expire(@key, Time.now.to_i + 15, gt: true)).to eq(false) 32 | expect(@redises.expire(@key, Time.now.to_i + 10, xx: true)).to eq(true) 33 | expect(@redises.expire(@key, Time.now.to_i + 10, nx: true)).to eq(false) 34 | expect(@redises.persist(@key)).to eq(true) 35 | expect(@redises.expire(@key, Time.now.to_i + 10, xx: true)).to eq(false) 36 | expect(@redises.expire(@key, Time.now.to_i + 10, nx: true)).to eq(true) 37 | end 38 | 39 | context '[mock only]' do 40 | # These are mock-only since we can't actually manipulate time in 41 | # the real Redis. 42 | 43 | before(:all) do 44 | @mock = @redises.mock 45 | end 46 | 47 | before do 48 | @now = Time.now 49 | allow(Time).to receive(:now).and_return(@now) 50 | end 51 | 52 | it_should_behave_like 'raises on invalid expire command options', :expireat 53 | 54 | it 'removes keys after enough time has passed' do 55 | @mock.expireat(@key, @now.to_i + 5) 56 | allow(Time).to receive(:now).and_return(@now + 5) 57 | expect(@mock.get(@key)).to be_nil 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/commands/flushall_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#flushall [mock only]' do 4 | # don't want to hurt things in the real redis that are outside our 5 | # namespace. 6 | before { @mock = @redises.mock } 7 | before { @key = 'mock-redis-test:select' } 8 | 9 | it "returns 'OK'" do 10 | expect(@mock.flushall).to eq('OK') 11 | end 12 | 13 | it 'removes all keys in the current DB' do 14 | @mock.set('k1', 'v1') 15 | @mock.lpush('k2', 'v2') 16 | 17 | @mock.flushall 18 | expect(@mock.keys('*')).to eq([]) 19 | end 20 | 21 | it 'removes all keys in other DBs, too' do 22 | @mock.set('k1', 'v1') 23 | 24 | @mock.select(1) 25 | @mock.flushall 26 | @mock.select(0) 27 | 28 | expect(@mock.get('k1')).to be_nil 29 | end 30 | 31 | it 'removes expiration times' do 32 | @mock.set('k1', 'v1') 33 | @mock.expire('k1', 360_000) 34 | @mock.flushall 35 | @mock.set('k1', 'v1') 36 | expect(@mock.ttl('k1')).to eq(-1) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/commands/flushdb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#flushdb [mock only]' do 4 | # don't want to hurt things in the real redis that are outside our 5 | # namespace. 6 | before { @mock = @redises.mock } 7 | before { @key = 'mock-redis-test:select' } 8 | 9 | it "returns 'OK'" do 10 | expect(@mock.flushdb).to eq('OK') 11 | end 12 | 13 | it 'removes all keys in the current DB' do 14 | @mock.set('k1', 'v1') 15 | @mock.lpush('k2', 'v2') 16 | 17 | @mock.flushdb 18 | expect(@mock.keys('*')).to eq([]) 19 | end 20 | 21 | it 'leaves other databases alone' do 22 | @mock.set('k1', 'v1') 23 | 24 | @mock.select(1) 25 | @mock.flushdb 26 | @mock.select(0) 27 | 28 | expect(@mock.get('k1')).to eq('v1') 29 | end 30 | 31 | it 'removes expiration times' do 32 | @mock.set('k1', 'v1') 33 | @mock.expire('k1', 360_000) 34 | @mock.flushdb 35 | @mock.set('k1', 'v1') 36 | expect(@mock.ttl('k1')).to eq(-1) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/commands/future_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe MockRedis::Future do 4 | let(:command) { [:get, 'foo'] } 5 | let(:result) { 'bar' } 6 | let(:block) { ->(value) { value.upcase } } 7 | 8 | before do 9 | @future = MockRedis::Future.new(command) 10 | @future2 = MockRedis::Future.new(command, block) 11 | end 12 | 13 | it 'remembers the command' do 14 | expect(@future.command).to eq(command) 15 | end 16 | 17 | it 'raises an error if the value is requested before the result is set' do 18 | expect { @future.value }.to raise_error(MockRedis::FutureNotReady) 19 | end 20 | 21 | it 'returns the value after the result has been set' do 22 | @future.store_result(result) 23 | expect(@future.value).to eq(result) 24 | end 25 | 26 | it 'executes the block on the value if block is passed in' do 27 | @future2.store_result(result) 28 | expect(@future2.value).to eq('BAR') 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/commands/geoadd_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#geoadd' do 4 | let(:key) { 'cities' } 5 | 6 | context 'with valid points' do 7 | let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] } 8 | let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] } 9 | let(:expected_result) do 10 | [['LA', 1.364461589564902e+15], ['SF', 1.367859319053696e+15]] 11 | end 12 | 13 | before { @redises.geoadd(key, *san_francisco, *los_angeles) } 14 | 15 | after { @redises.zrem(key, %w[SF LA]) } 16 | 17 | it 'adds members to ZSET' do 18 | cities = @redises.zrange(key, 0, -1, with_scores: true) 19 | expect(cities).to be == expected_result 20 | end 21 | end 22 | 23 | context 'with invalid points' do 24 | context 'when number of arguments wrong' do 25 | let(:message) { /ERR wrong number of arguments for 'geoadd' command/ } 26 | 27 | it 'raises Redis::CommandError' do 28 | expect { @redises.geoadd(key, 1, 1) } 29 | .to raise_error(Redis::CommandError, message) 30 | end 31 | end 32 | 33 | context 'when coordinates are not in allowed range' do 34 | let(:coords) { [181, 86] } 35 | let(:message) do 36 | formatted_coords = coords.map { |c| format('%.6f', coords: c) } 37 | /ERR invalid longitude,latitude pair #{formatted_coords.join(',')}/ 38 | end 39 | 40 | after { @redises.zrem(key, 'SF') } 41 | 42 | it 'raises Redis::CommandError' do 43 | expect { @redises.geoadd(key, *coords, 'SF') } 44 | .to raise_error(Redis::CommandError, message) 45 | end 46 | end 47 | 48 | context 'when coordinates are not valid floats' do 49 | let(:coords) { ['x', 35] } 50 | let(:message) { /ERR value is not a valid float/ } 51 | 52 | it 'raises Redis::CommandError' do 53 | expect { @redises.geoadd key, *coords, 'SF' } 54 | .to raise_error(Redis::CommandError, message) 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/commands/geohash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#geohash' do 4 | let(:key) { 'cities' } 5 | 6 | context 'with existing key' do 7 | let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] } 8 | let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] } 9 | 10 | before { @redises.geoadd(key, *san_francisco, *los_angeles) } 11 | 12 | after { @redises.zrem(key, %w[SF LA]) } 13 | 14 | context 'with existing points only' do 15 | let(:expected_result) do 16 | %w[9q8yu38ejp0 9q59e171je0] 17 | end 18 | 19 | it 'returns decoded coordinates pairs for each point' do 20 | results = @redises.geohash(key, %w[SF LA]) 21 | expect(results).to be == expected_result 22 | end 23 | 24 | context 'with non-existing points only' do 25 | it 'returns array filled with nils' do 26 | results = @redises.geohash(key, %w[FF FA]) 27 | expect(results).to be == [nil, nil] 28 | end 29 | end 30 | 31 | context 'with both existing and non-existing points' do 32 | let(:expected_result) do 33 | ['9q8yu38ejp0', nil] 34 | end 35 | 36 | it 'returns mixture of nil and coordinates pair' do 37 | results = @redises.geohash(key, %w[SF FA]) 38 | expect(results).to be == expected_result 39 | end 40 | end 41 | end 42 | end 43 | 44 | context 'with non-existing key' do 45 | before { @redises.del(key) } 46 | 47 | it 'returns empty array' do 48 | results = @redises.geohash(key, %w[SF LA]) 49 | expect(results).to be == [nil, nil] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/commands/geopos_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#geopos' do 4 | let(:key) { 'cities' } 5 | 6 | context 'with existing key' do 7 | let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] } 8 | let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] } 9 | 10 | before { @redises.geoadd(key, *san_francisco, *los_angeles) } 11 | 12 | after { @redises.zrem(key, %w[SF LA]) } 13 | 14 | context 'with existing points only' do 15 | let(:expected_result) do 16 | [ 17 | %w[-122.5076410174369812 37.75781598995183685], 18 | %w[-118.69192510843276978 34.020729570911179] 19 | ] 20 | end 21 | 22 | it 'returns decoded coordinates pairs for each point' do 23 | coords = @redises.geopos(key, %w[SF LA]) 24 | expect(coords).to be == expected_result 25 | end 26 | 27 | context 'with non-existing points only' do 28 | it 'returns array filled with nils' do 29 | coords = @redises.geopos(key, %w[FF FA]) 30 | expect(coords).to be == [nil, nil] 31 | end 32 | end 33 | 34 | context 'with both existing and non-existing points' do 35 | let(:expected_result) do 36 | [%w[-122.5076410174369812 37.75781598995183685], nil] 37 | end 38 | 39 | it 'returns mixture of nil and coordinates pair' do 40 | coords = @redises.geopos(key, %w[SF FA]) 41 | expect(coords).to be == expected_result 42 | end 43 | end 44 | end 45 | end 46 | 47 | context 'with non-existing key' do 48 | before { @redises.del(key) } 49 | 50 | it 'returns empty array' do 51 | coords = @redises.geopos(key, %w[SF LA]) 52 | expect(coords).to be == [nil, nil] 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/commands/get_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#get(key)' do 4 | before do 5 | @key = 'mock-redis-test:73288' 6 | end 7 | 8 | it 'returns nil for a nonexistent value' do 9 | expect(@redises.get('mock-redis-test:does-not-exist')).to be_nil 10 | end 11 | 12 | it 'returns a stored string value' do 13 | @redises.set(@key, 'forsooth') 14 | expect(@redises.get(@key)).to eq('forsooth') 15 | end 16 | 17 | it 'treats integers as strings' do 18 | @redises.set(@key, 100) 19 | expect(@redises.get(@key)).to eq('100') 20 | end 21 | 22 | it 'stringifies key' do 23 | key = :a_symbol 24 | 25 | @redises.set(key, 'hello') 26 | expect(@redises.get(key.to_s)).to eq('hello') 27 | expect(@redises.get(key)).to eq('hello') 28 | end 29 | 30 | it_should_behave_like 'a string-only command' 31 | end 32 | -------------------------------------------------------------------------------- /spec/commands/getbit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#getbit(key, offset)' do 4 | before do 5 | @key = 'mock-redis-test:getbit' 6 | @redises.set(@key, 'h') # ASCII 0x68 7 | end 8 | 9 | it 'gets the bits from the key' do 10 | expect(@redises.getbit(@key, 0)).to eq(0) 11 | expect(@redises.getbit(@key, 1)).to eq(1) 12 | expect(@redises.getbit(@key, 2)).to eq(1) 13 | expect(@redises.getbit(@key, 3)).to eq(0) 14 | expect(@redises.getbit(@key, 4)).to eq(1) 15 | expect(@redises.getbit(@key, 5)).to eq(0) 16 | expect(@redises.getbit(@key, 6)).to eq(0) 17 | expect(@redises.getbit(@key, 7)).to eq(0) 18 | end 19 | 20 | it 'returns 0 for out-of-range bits' do 21 | expect(@redises.getbit(@key, 100)).to eq(0) 22 | end 23 | 24 | it 'does not modify the stored value for out-of-range bits' do 25 | @redises.getbit(@key, 100) 26 | expect(@redises.get(@key)).to eq('h') 27 | end 28 | 29 | it 'treats nonexistent keys as empty strings' do 30 | expect(@redises.getbit('mock-redis-test:not-found', 0)).to eq(0) 31 | end 32 | 33 | it_should_behave_like 'a string-only command' 34 | end 35 | -------------------------------------------------------------------------------- /spec/commands/getdel.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#getdel(key)' do 4 | before do 5 | @key = 'mock-redis-test:73288' 6 | end 7 | 8 | it 'returns nil for a nonexistent value' do 9 | expect(@redises.getdel('mock-redis-test:does-not-exist')).to be_nil 10 | end 11 | 12 | it 'returns a stored string value' do 13 | @redises.set(@key, 'forsooth') 14 | expect(@redises.getdel(@key)).to eq('forsooth') 15 | end 16 | 17 | it 'deletes the key after returning it' do 18 | @redises.set(@key, 'forsooth') 19 | @redises.getdel(@key) 20 | expect(@redises.get(@key)).to be_nil 21 | end 22 | 23 | it_should_behave_like 'a string-only command' 24 | end 25 | -------------------------------------------------------------------------------- /spec/commands/getrange_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#getrange(key, start, stop)' do 4 | before do 5 | @key = 'mock-redis-test:getrange' 6 | @redises.set(@key, 'This is a string') 7 | end 8 | 9 | it 'returns a substring' do 10 | expect(@redises.getrange(@key, 0, 3)).to eq('This') 11 | end 12 | 13 | it 'works with negative indices' do 14 | expect(@redises.getrange(@key, -3, -1)).to eq('ing') 15 | end 16 | 17 | it 'limits the result to the actual length of the string' do 18 | expect(@redises.getrange(@key, 10, 100)).to eq('string') 19 | end 20 | 21 | it_should_behave_like 'a string-only command' 22 | end 23 | -------------------------------------------------------------------------------- /spec/commands/getset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#getrange(key, value)' do 4 | before do 5 | @key = 'mock-redis-test:getset' 6 | @redises.set(@key, 'oldvalue') 7 | end 8 | 9 | it 'returns the old value' do 10 | expect(@redises.getset(@key, 'newvalue')).to eq('oldvalue') 11 | end 12 | 13 | it 'sets the value to the new value' do 14 | @redises.getset(@key, 'newvalue') 15 | expect(@redises.get(@key)).to eq('newvalue') 16 | end 17 | 18 | it 'returns nil for nonexistent keys' do 19 | expect(@redises.getset('mock-redis-test:not-found', 1)).to be_nil 20 | end 21 | 22 | it_should_behave_like 'a string-only command' 23 | end 24 | -------------------------------------------------------------------------------- /spec/commands/hdel_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hdel(key, field)' do 4 | before do 5 | @key = 'mock-redis-test:hdel' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns 1 when it removes a field' do 11 | expect(@redises.hdel(@key, 'k1')).to eq(1) 12 | end 13 | 14 | it 'returns 0 when it does not remove a field' do 15 | expect(@redises.hdel(@key, 'nonesuch')).to eq(0) 16 | end 17 | 18 | it 'actually removes the field' do 19 | @redises.hdel(@key, 'k1') 20 | expect(@redises.hget(@key, 'k1')).to be_nil 21 | end 22 | 23 | it 'treats the field as a string' do 24 | field = 2 25 | @redises.hset(@key, field, 'two') 26 | @redises.hdel(@key, field) 27 | expect(@redises.hget(@key, field)).to be_nil 28 | end 29 | 30 | it 'removes only the field specified' do 31 | @redises.hdel(@key, 'k1') 32 | expect(@redises.hget(@key, 'k2')).to eq('v2') 33 | end 34 | 35 | it 'cleans up empty hashes' do 36 | @redises.hdel(@key, 'k1') 37 | @redises.hdel(@key, 'k2') 38 | expect(@redises.get(@key)).to be_nil 39 | end 40 | 41 | it 'supports a variable number of arguments' do 42 | @redises.hdel(@key, 'k1', 'k2') 43 | expect(@redises.get(@key)).to be_nil 44 | end 45 | 46 | it 'treats variable arguments as strings' do 47 | field = 2 48 | @redises.hset(@key, field, 'two') 49 | @redises.hdel(@key, field) 50 | expect(@redises.hget(@key, field)).to be_nil 51 | end 52 | 53 | it 'supports a variable number of fields as array' do 54 | expect(@redises.hdel(@key, %w[k1 k2])).to eq(2) 55 | 56 | expect(@redises.hget(@key, 'k1')).to be_nil 57 | expect(@redises.hget(@key, 'k2')).to be_nil 58 | expect(@redises.get(@key)).to be_nil 59 | end 60 | 61 | it 'supports a list of fields in various way' do 62 | expect(@redises.hdel(@key, ['k1'], 'k2')).to eq(2) 63 | 64 | expect(@redises.hget(@key, 'k1')).to be_nil 65 | expect(@redises.hget(@key, 'k2')).to be_nil 66 | expect(@redises.get(@key)).to be_nil 67 | end 68 | 69 | it 'raises error if an empty array is passed' do 70 | expect { @redises.hdel(@key, []) }.to raise_error( 71 | Redis::CommandError, 72 | /ERR wrong number of arguments for 'hdel' command/ 73 | ) 74 | end 75 | 76 | it_should_behave_like 'a hash-only command' 77 | end 78 | -------------------------------------------------------------------------------- /spec/commands/hexists_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hexists(key, field)' do 4 | before do 5 | @key = 'mock-redis-test:hexists' 6 | @redises.hset(@key, 'field', 'value') 7 | end 8 | 9 | it 'returns true if the hash has that field' do 10 | expect(@redises.hexists(@key, 'field')).to eq(true) 11 | end 12 | 13 | it 'returns false if the hash lacks that field' do 14 | expect(@redises.hexists(@key, 'nonesuch')).to eq(false) 15 | end 16 | 17 | it 'treats the field as a string' do 18 | @redises.hset(@key, 1, 'one') 19 | expect(@redises.hexists(@key, 1)).to eq(true) 20 | end 21 | 22 | it 'returns nil when there is no such key' do 23 | expect(@redises.hexists('mock-redis-test:nonesuch', 'key')).to eq(false) 24 | end 25 | 26 | it_should_behave_like 'a hash-only command' 27 | end 28 | -------------------------------------------------------------------------------- /spec/commands/hget_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hget(key, field)' do 4 | before do 5 | @key = 'mock-redis-test:hget' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns the value stored at field' do 11 | expect(@redises.hget(@key, 'k1')).to eq('v1') 12 | end 13 | 14 | it 'treats the field as a string' do 15 | @redises.hset(@key, '3', 'v3') 16 | expect(@redises.hget(@key, 3)).to eq('v3') 17 | end 18 | 19 | it 'returns nil when there is no such field' do 20 | expect(@redises.hget(@key, 'nonesuch')).to be_nil 21 | end 22 | 23 | it 'returns nil when there is no such key' do 24 | expect(@redises.hget('mock-redis-test:nonesuch', 'k1')).to be_nil 25 | end 26 | 27 | it_should_behave_like 'a hash-only command' 28 | end 29 | -------------------------------------------------------------------------------- /spec/commands/hgetall_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hgetall(key)' do 4 | before do 5 | @key = 'mock-redis-test:hgetall' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns the (key, value) pairs stored in the hash' do 11 | expect(@redises.hgetall(@key)).to eq( 12 | { 13 | 'k1' => 'v1', 14 | 'k2' => 'v2', 15 | } 16 | ) 17 | end 18 | 19 | it 'returns [] when there is no such key' do 20 | expect(@redises.hgetall('mock-redis-test:nonesuch')).to eq({}) 21 | end 22 | 23 | it "doesn't return a mutable reference to the returned data" do 24 | mr = MockRedis.new 25 | mr.hset(@key, 'k1', 'v1') 26 | mr.hset(@key, 'k2', 'v2') 27 | hash = mr.hgetall(@key) 28 | hash['dont'] = 'mutate' 29 | new_hash = mr.hgetall(@key) 30 | expect(new_hash.keys.sort).to eq(%w[k1 k2]) 31 | end 32 | 33 | it_should_behave_like 'a hash-only command' 34 | end 35 | -------------------------------------------------------------------------------- /spec/commands/hincrby_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hincrby(key, field, increment)' do 4 | before do 5 | @key = 'mock-redis-test:hincrby' 6 | @field = 'count' 7 | end 8 | 9 | it 'returns the value after the increment' do 10 | @redises.hset(@key, @field, 2) 11 | expect(@redises.hincrby(@key, @field, 2)).to eq(4) 12 | end 13 | 14 | it 'treats a missing key like 0' do 15 | expect(@redises.hincrby(@key, @field, 1)).to eq(1) 16 | end 17 | 18 | it 'creates a hash if nothing is present' do 19 | @redises.hincrby(@key, @field, 1) 20 | expect(@redises.hget(@key, @field)).to eq('1') 21 | end 22 | 23 | it 'increments negative numbers' do 24 | @redises.hset(@key, @field, -10) 25 | expect(@redises.hincrby(@key, @field, 2)).to eq(-8) 26 | end 27 | 28 | it 'works multiple times' do 29 | expect(@redises.hincrby(@key, @field, 2)).to eq(2) 30 | expect(@redises.hincrby(@key, @field, 2)).to eq(4) 31 | expect(@redises.hincrby(@key, @field, 2)).to eq(6) 32 | end 33 | 34 | it 'accepts an integer-ish string' do 35 | expect(@redises.hincrby(@key, @field, '2')).to eq(2) 36 | end 37 | 38 | it 'treats the field as a string' do 39 | field = 11 40 | @redises.hset(@key, field, 2) 41 | expect(@redises.hincrby(@key, field, 2)).to eq(4) 42 | end 43 | 44 | it 'raises an error if the value does not look like an integer' do 45 | @redises.hset(@key, @field, 'one') 46 | expect do 47 | @redises.hincrby(@key, @field, 1) 48 | end.to raise_error(Redis::CommandError) 49 | end 50 | 51 | it 'raises an error if the delta does not look like an integer' do 52 | expect do 53 | @redises.hincrby(@key, @field, 'foo') 54 | end.to raise_error(ArgumentError) 55 | end 56 | 57 | it_should_behave_like 'a hash-only command' 58 | end 59 | -------------------------------------------------------------------------------- /spec/commands/hincrbyfloat_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hincrbyfloat(key, field, increment)' do 4 | before do 5 | @key = 'mock-redis-test:hincrbyfloat' 6 | @field = 'count' 7 | end 8 | 9 | it 'returns the value after the increment' do 10 | @redises.hset(@key, @field, 2.0) 11 | expect(@redises.hincrbyfloat(@key, @field, 2.1)).to be_within(0.0001).of(4.1) 12 | end 13 | 14 | it 'treats a missing key like 0' do 15 | expect(@redises.hincrbyfloat(@key, @field, 1.2)).to be_within(0.0001).of(1.2) 16 | end 17 | 18 | it 'creates a hash if nothing is present' do 19 | @redises.hincrbyfloat(@key, @field, 1.0) 20 | expect(@redises.hget(@key, @field)).to eq('1') 21 | end 22 | 23 | it 'increments negative numbers' do 24 | @redises.hset(@key, @field, -10.4) 25 | expect(@redises.hincrbyfloat(@key, @field, 2.3)).to be_within(0.0001).of(-8.1) 26 | end 27 | 28 | it 'works multiple times' do 29 | expect(@redises.hincrbyfloat(@key, @field, 2.1)).to be_within(0.0001).of(2.1) 30 | expect(@redises.hincrbyfloat(@key, @field, 2.2)).to be_within(0.0001).of(4.3) 31 | expect(@redises.hincrbyfloat(@key, @field, 2.3)).to be_within(0.0001).of(6.6) 32 | end 33 | 34 | it 'accepts a float-ish string' do 35 | expect(@redises.hincrbyfloat(@key, @field, '2.2')).to be_within(0.0001).of(2.2) 36 | end 37 | 38 | it 'treats the field as a string' do 39 | field = 11 40 | @redises.hset(@key, field, 2) 41 | expect(@redises.hincrbyfloat(@key, field, 2)).to eq(4.0) 42 | end 43 | 44 | it 'raises an error if the value does not look like a float' do 45 | @redises.hset(@key, @field, 'one.two') 46 | expect do 47 | @redises.hincrbyfloat(@key, @field, 1) 48 | end.to raise_error(Redis::CommandError) 49 | end 50 | 51 | it 'raises an error if the delta does not look like a float' do 52 | expect do 53 | @redises.hincrbyfloat(@key, @field, 'foobar.baz') 54 | end.to raise_error(ArgumentError) 55 | end 56 | 57 | it_should_behave_like 'a hash-only command' 58 | end 59 | -------------------------------------------------------------------------------- /spec/commands/hkeys_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hkeys(key)' do 4 | before do 5 | @key = 'mock-redis-test:hkeys' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns the keys stored in the hash' do 11 | expect(@redises.hkeys(@key).sort).to eq(%w[k1 k2]) 12 | end 13 | 14 | it 'returns [] when there is no such key' do 15 | expect(@redises.hkeys('mock-redis-test:nonesuch')).to eq([]) 16 | end 17 | 18 | it_should_behave_like 'a hash-only command' 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/hlen_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hlen(key)' do 4 | before do 5 | @key = 'mock-redis-test:hlen' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns the number of keys stored in the hash' do 11 | expect(@redises.hlen(@key)).to eq(2) 12 | end 13 | 14 | it 'returns [] when there is no such key' do 15 | expect(@redises.hlen('mock-redis-test:nonesuch')).to eq(0) 16 | end 17 | 18 | it_should_behave_like 'a hash-only command' 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/hmget_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hmget(key, field [, field, ...])' do 4 | before do 5 | @key = 'mock-redis-test:hmget' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns the values for those keys' do 11 | expect(@redises.hmget(@key, 'k1', 'k2').sort).to eq(%w[v1 v2]) 12 | end 13 | 14 | it 'treats an array as multiple keys' do 15 | expect(@redises.hmget(@key, %w[k1 k2]).sort).to eq(%w[v1 v2]) 16 | end 17 | 18 | it 'treats the fielsd as strings' do 19 | @redises.hset(@key, 1, 'one') 20 | @redises.hset(@key, 2, 'two') 21 | expect(@redises.hmget(@key, 1, 2).sort).to eq(%w[one two]) 22 | end 23 | 24 | it 'returns nils when there are no such fields' do 25 | expect(@redises.hmget(@key, 'k1', 'mock-redis-test:nonesuch')). 26 | to eq(['v1', nil]) 27 | end 28 | 29 | it 'returns nils when there is no such key' do 30 | expect(@redises.hmget(@key, 'mock-redis-test:nonesuch')).to eq([nil]) 31 | end 32 | 33 | it 'raises an error if given no fields' do 34 | expect do 35 | @redises.hmget(@key) 36 | end.to raise_error(Redis::CommandError) 37 | end 38 | 39 | it 'raises an error if given an empty list of fields' do 40 | expect do 41 | @redises.hmget(@key, []) 42 | end.to raise_error(Redis::CommandError) 43 | end 44 | 45 | it_should_behave_like 'a hash-only command' 46 | end 47 | -------------------------------------------------------------------------------- /spec/commands/hmset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hmset(key, field, value [, field, value ...])' do 4 | before do 5 | @key = 'mock-redis-test:hmset' 6 | end 7 | 8 | it "returns 'OK'" do 9 | expect(@redises.hmset(@key, 'k1', 'v1', 'k2', 'v2')).to eq('OK') 10 | end 11 | 12 | it 'sets the values' do 13 | @redises.hmset(@key, 'k1', 'v1', 'k2', 'v2') 14 | expect(@redises.hmget(@key, 'k1', 'k2')).to eq(%w[v1 v2]) 15 | end 16 | 17 | it 'updates an existing hash' do 18 | @redises.hset(@key, 'foo', 'bar') 19 | @redises.hmset(@key, 'bert', 'ernie', 'diet', 'coke') 20 | 21 | expect(@redises.hmget(@key, 'foo', 'bert', 'diet')). 22 | to eq(%w[bar ernie coke]) 23 | end 24 | 25 | it 'stores the values as strings' do 26 | @redises.hmset(@key, 'one', 1) 27 | expect(@redises.hget(@key, 'one')).to eq('1') 28 | end 29 | 30 | it 'raises an error if given no fields or values' do 31 | expect do 32 | @redises.hmset(@key) 33 | end.to raise_error(Redis::CommandError) 34 | end 35 | 36 | it 'raises an error if given an odd number of fields+values' do 37 | expect do 38 | @redises.hmset(@key, 'k1', 1, 'k2') 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | 42 | # The following tests address https://github.com/sds/mock_redis/issues/170 43 | context 'keys are stored as strings' do 44 | before do 45 | @redises.hmset(1, :foo, :bar) 46 | @redises.hmset(:a_sym, :boo, :bas) 47 | end 48 | 49 | it { expect(@redises.hgetall('1')).to eq @redises.hgetall(1) } 50 | it { expect(@redises.hgetall('a_sym')).to eq @redises.hgetall(:a_sym) } 51 | it { expect(@redises.del('1')).to eq 1 } 52 | it { expect(@redises.del(1)).to eq 1 } 53 | it { expect(@redises.del('a_sym')).to eq 1 } 54 | it { expect(@redises.del(:a_sym)).to eq 1 } 55 | 56 | after do 57 | @redises.del(1) 58 | @redises.del(:a_sym) 59 | end 60 | end 61 | 62 | # The following tests address https://github.com/sds/mock_redis/issues/134 63 | context 'hmset accepts an array as its only argument' do 64 | it { expect(@redises.hmset([@key, :bar, :bas])).to eq 'OK' } 65 | it { expect { @redises.hmset([:foo, :bar]) }.to raise_error(Redis::CommandError) } 66 | end 67 | 68 | it_should_behave_like 'a hash-only command' 69 | end 70 | -------------------------------------------------------------------------------- /spec/commands/hscan_each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hscan_each' do 4 | subject { MockRedis::Database.new(self) } 5 | 6 | let(:key) { 'mock-redis-test:hscan_each' } 7 | 8 | before do 9 | allow(subject).to receive(:hgetall).and_return(collection) 10 | end 11 | 12 | context 'when no keys are found' do 13 | let(:collection) { [] } 14 | 15 | it 'does not iterate over any elements' do 16 | results = subject.hscan_each(key).map do |k, v| 17 | [k, v] 18 | end 19 | expect(results).to match_array(collection) 20 | end 21 | end 22 | 23 | context 'when keys are found' do 24 | context 'when no match filter is supplied' do 25 | let(:collection) { Array.new(20) { |i| ["k#{i}", "v#{i}"] } } 26 | 27 | it 'iterates over each item in the collection' do 28 | results = subject.hscan_each(key).map do |k, v| 29 | [k, v] 30 | end 31 | expect(results).to match_array(collection) 32 | end 33 | end 34 | 35 | context 'when giving a custom match filter' do 36 | let(:match) { 'k1*' } 37 | let(:collection) { Array.new(12) { |i| ["k#{i}", "v#{i}"] } } 38 | let(:expected) { [%w[k1 v1], %w[k10 v10], %w[k11 v11]] } 39 | 40 | it 'iterates over each item in the filtered collection' do 41 | results = subject.hscan_each(key, match: match).map do |k, v| 42 | [k, v] 43 | end 44 | expect(results).to match_array(expected) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/commands/hscan_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hscan' do 4 | let(:count) { 10 } 5 | let(:match) { '*' } 6 | let(:key) { 'mock-redis-test:hscan' } 7 | 8 | context 'when the hash does not exist' do 9 | it 'returns a 0 cursor and an empty collection' do 10 | expect(@redises.hscan(key, 0, count: count, match: match)).to eq(['0', []]) 11 | end 12 | end 13 | 14 | context 'when the hash exists' do 15 | before do 16 | @redises.hset(key, 'k1', 'v1') 17 | @redises.hset(key, 'k2', 'v2') 18 | @redises.hset(key, 'k3', 'v3') 19 | end 20 | 21 | let(:expected) { ['0', [%w[k1 v1], %w[k2 v2], %w[k3 v3]]] } 22 | 23 | it 'returns a 0 cursor and the collection' do 24 | expect(@redises.hscan(key, 0, count: 10)).to eq(expected) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/commands/hset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hset(key, field)' do 4 | before do 5 | @key = 'mock-redis-test:hset' 6 | end 7 | 8 | it 'returns 1 if the key does not exist' do 9 | expect(@redises.hset(@key, 'k1', 'v1')).to eq(1) 10 | end 11 | 12 | it 'returns 1 if the key exists but the field does not' do 13 | @redises.hset(@key, 'k1', 'v1') 14 | expect(@redises.hset(@key, 'k2', 'v2')).to eq(1) 15 | end 16 | 17 | it 'returns 0 if the field already exists' do 18 | @redises.hset(@key, 'k1', 'v1') 19 | expect(@redises.hset(@key, 'k1', 'v1')).to eq(0) 20 | end 21 | 22 | it 'creates a hash there is no such field' do 23 | @redises.hset(@key, 'k1', 'v1') 24 | expect(@redises.hget(@key, 'k1')).to eq('v1') 25 | end 26 | 27 | it 'stores values as strings' do 28 | @redises.hset(@key, 'num', 1) 29 | expect(@redises.hget(@key, 'num')).to eq('1') 30 | end 31 | 32 | it 'stores fields as strings' do 33 | @redises.hset(@key, 1, 'one') 34 | expect(@redises.hget(@key, '1')).to eq('one') 35 | end 36 | 37 | it 'stores fields sent in a hash' do 38 | expect(@redises.hset(@key, { 'k1' => 'v1', 'k2' => 'v2' })).to eq(2) 39 | end 40 | 41 | it 'stores array values correctly' do 42 | @redises.hset(@key, %w[k1 v1 k2 v2]) 43 | expect(@redises.hget(@key, 'k1')).to eq('v1') 44 | expect(@redises.hget(@key, 'k2')).to eq('v2') 45 | end 46 | 47 | it 'raises error when key is nil' do 48 | expect do 49 | @redises.hset(nil, 'abc') 50 | end.to raise_error(TypeError) 51 | end 52 | 53 | it 'raises error when hash key is nil' do 54 | expect do 55 | @redises.hset(@key, nil, 'abc') 56 | end.to raise_error(TypeError) 57 | end 58 | 59 | it 'stores multiple arguments correctly' do 60 | @redises.hset(@key, 'k1', 'v1', 'k2', 'v2') 61 | expect(@redises.hget(@key, 'k1')).to eq('v1') 62 | expect(@redises.hget(@key, 'k2')).to eq('v2') 63 | end 64 | 65 | it_should_behave_like 'a hash-only command' 66 | end 67 | -------------------------------------------------------------------------------- /spec/commands/hsetnx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hsetnx(key, field)' do 4 | before do 5 | @key = 'mock-redis-test:hsetnx' 6 | end 7 | 8 | it 'returns true if the field is absent' do 9 | expect(@redises.hsetnx(@key, 'field', 'val')).to eq(true) 10 | end 11 | 12 | it 'returns 0 if the field is present' do 13 | @redises.hset(@key, 'field', 'val') 14 | expect(@redises.hsetnx(@key, 'field', 'val')).to eq(false) 15 | end 16 | 17 | it 'leaves the field unchanged if the field is present' do 18 | @redises.hset(@key, 'field', 'old') 19 | @redises.hsetnx(@key, 'field', 'new') 20 | expect(@redises.hget(@key, 'field')).to eq('old') 21 | end 22 | 23 | it 'sets the field if the field is absent' do 24 | @redises.hsetnx(@key, 'field', 'new') 25 | expect(@redises.hget(@key, 'field')).to eq('new') 26 | end 27 | 28 | it 'creates a hash if there is no such field' do 29 | @redises.hsetnx(@key, 'field', 'val') 30 | expect(@redises.hget(@key, 'field')).to eq('val') 31 | end 32 | 33 | it 'stores values as strings' do 34 | @redises.hsetnx(@key, 'num', 1) 35 | expect(@redises.hget(@key, 'num')).to eq('1') 36 | end 37 | 38 | it 'stores fields as strings' do 39 | @redises.hsetnx(@key, 1, 'one') 40 | expect(@redises.hget(@key, '1')).to eq('one') 41 | end 42 | 43 | it_should_behave_like 'a hash-only command' 44 | end 45 | -------------------------------------------------------------------------------- /spec/commands/hvals_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#hvals(key)' do 4 | before do 5 | @key = 'mock-redis-test:hvals' 6 | @redises.hset(@key, 'k1', 'v1') 7 | @redises.hset(@key, 'k2', 'v2') 8 | end 9 | 10 | it 'returns the values stored in the hash' do 11 | expect(@redises.hvals(@key).sort).to eq(%w[v1 v2]) 12 | end 13 | 14 | it 'returns [] when there is no such key' do 15 | expect(@redises.hvals('mock-redis-test:nonesuch')).to eq([]) 16 | end 17 | 18 | it_should_behave_like 'a hash-only command' 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/incr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#incr(key)' do 4 | before { @key = 'mock-redis-test:33888' } 5 | 6 | it 'returns the value after the increment' do 7 | @redises.set(@key, 1) 8 | expect(@redises.incr(@key)).to eq(2) 9 | end 10 | 11 | it 'treats a missing key like 0' do 12 | expect(@redises.incr(@key)).to eq(1) 13 | end 14 | 15 | it 'increments negative numbers' do 16 | @redises.set(@key, -10) 17 | expect(@redises.incr(@key)).to eq(-9) 18 | end 19 | 20 | it 'works multiple times' do 21 | expect(@redises.incr(@key)).to eq(1) 22 | expect(@redises.incr(@key)).to eq(2) 23 | expect(@redises.incr(@key)).to eq(3) 24 | end 25 | 26 | it 'raises an error if the value does not look like an integer' do 27 | @redises.set(@key, 'one') 28 | expect do 29 | @redises.incr(@key) 30 | end.to raise_error(Redis::CommandError) 31 | end 32 | 33 | it_should_behave_like 'a string-only command' 34 | end 35 | -------------------------------------------------------------------------------- /spec/commands/incrby_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#incrby(key, increment)' do 4 | before { @key = 'mock-redis-test:65374' } 5 | 6 | it 'returns the value after the increment' do 7 | @redises.set(@key, 2) 8 | expect(@redises.incrby(@key, 2)).to eq(4) 9 | end 10 | 11 | it 'treats a missing key like 0' do 12 | expect(@redises.incrby(@key, 1)).to eq(1) 13 | end 14 | 15 | it 'increments negative numbers' do 16 | @redises.set(@key, -10) 17 | expect(@redises.incrby(@key, 2)).to eq(-8) 18 | end 19 | 20 | it 'works multiple times' do 21 | expect(@redises.incrby(@key, 2)).to eq(2) 22 | expect(@redises.incrby(@key, 2)).to eq(4) 23 | expect(@redises.incrby(@key, 2)).to eq(6) 24 | end 25 | 26 | it 'accepts an integer-ish string' do 27 | expect(@redises.incrby(@key, '2')).to eq(2) 28 | end 29 | 30 | it 'raises an error if the value does not look like an integer' do 31 | @redises.set(@key, 'one') 32 | expect do 33 | @redises.incrby(@key, 1) 34 | end.to raise_error(Redis::CommandError) 35 | end 36 | 37 | it 'raises an error if the delta does not look like an integer' do 38 | expect do 39 | @redises.incrby(@key, 'foo') 40 | end.to raise_error(ArgumentError) 41 | end 42 | 43 | it_should_behave_like 'a string-only command' 44 | end 45 | -------------------------------------------------------------------------------- /spec/commands/incrbyfloat_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#incrbyfloat(key, increment)' do 4 | before { @key = 'mock-redis-test:65374' } 5 | 6 | it 'returns the value after the increment' do 7 | @redises.set(@key, 2.0) 8 | expect(@redises.incrbyfloat(@key, 2.1)).to be_within(0.0001).of(4.1) 9 | end 10 | 11 | it 'treats a missing key like 0' do 12 | expect(@redises.incrbyfloat(@key, 1.2)).to be_within(0.0001).of(1.2) 13 | end 14 | 15 | it 'increments negative numbers' do 16 | @redises.set(@key, -10.4) 17 | expect(@redises.incrbyfloat(@key, 2.3)).to be_within(0.0001).of(-8.1) 18 | end 19 | 20 | it 'works multiple times' do 21 | expect(@redises.incrbyfloat(@key, 2.1)).to be_within(0.0001).of(2.1) 22 | expect(@redises.incrbyfloat(@key, 2.2)).to be_within(0.0001).of(4.3) 23 | expect(@redises.incrbyfloat(@key, 2.3)).to be_within(0.0001).of(6.6) 24 | end 25 | 26 | it 'accepts an float-ish string' do 27 | expect(@redises.incrbyfloat(@key, '2.2')).to be_within(0.0001).of(2.2) 28 | end 29 | 30 | it 'raises an error if the value does not look like an float' do 31 | @redises.set(@key, 'one.two') 32 | expect do 33 | @redises.incrbyfloat(@key, 1) 34 | end.to raise_error(Redis::CommandError) 35 | end 36 | 37 | it 'raises an error if the delta does not look like an float' do 38 | expect do 39 | @redises.incrbyfloat(@key, 'foobar.baz') 40 | end.to raise_error(ArgumentError) 41 | end 42 | 43 | it_should_behave_like 'a string-only command' 44 | end 45 | -------------------------------------------------------------------------------- /spec/commands/info_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#info [mock only]' do 4 | let(:redis) { @redises.mock } 5 | 6 | it 'responds with a config hash' do 7 | expect(redis.info).to be_a(Hash) 8 | end 9 | 10 | it 'gets default set of info' do 11 | info = redis.info 12 | expect(info['arch_bits']).to be_a(String) 13 | expect(info['connected_clients']).to be_a(String) 14 | expect(info['aof_rewrite_scheduled']).to be_a(String) 15 | expect(info['used_cpu_sys']).to be_a(String) 16 | end 17 | 18 | it 'gets all info' do 19 | info = redis.info(:all) 20 | expect(info['arch_bits']).to be_a(String) 21 | expect(info['connected_clients']).to be_a(String) 22 | expect(info['aof_rewrite_scheduled']).to be_a(String) 23 | expect(info['used_cpu_sys']).to be_a(String) 24 | expect(info['cmdstat_slowlog']).to be_a(String) 25 | end 26 | 27 | it 'gets server info' do 28 | expect(redis.info(:server)['arch_bits']).to be_a(String) 29 | end 30 | 31 | it 'gets clients info' do 32 | expect(redis.info(:clients)['connected_clients']).to be_a(String) 33 | end 34 | 35 | it 'gets memory info' do 36 | expect(redis.info(:memory)['used_memory']).to be_a(String) 37 | end 38 | 39 | it 'gets persistence info' do 40 | expect(redis.info(:persistence)['aof_rewrite_scheduled']).to be_a(String) 41 | end 42 | 43 | it 'gets stats info' do 44 | expect(redis.info(:stats)['keyspace_hits']).to be_a(String) 45 | end 46 | 47 | it 'gets replication info' do 48 | expect(redis.info(:replication)['role']).to be_a(String) 49 | end 50 | 51 | it 'gets cpu info' do 52 | expect(redis.info(:cpu)['used_cpu_sys']).to be_a(String) 53 | end 54 | 55 | it 'gets keyspace info' do 56 | expect(redis.info(:keyspace)['db0']).to be_a(String) 57 | end 58 | 59 | it 'gets commandstats info' do 60 | expect(redis.info(:commandstats)['sunionstore']['usec']).to be_a(String) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/commands/lastsave_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lastsave [mock only]' do 4 | # can't test against both since it's timing-dependent 5 | it 'returns a Unix time' do 6 | expect(@redises.mock.lastsave.to_s).to match(/^\d+$/) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/commands/lindex_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lindex(key, index)' do 4 | before { @key = 'mock-redis-test:69312' } 5 | 6 | it 'gets an element from the list by its index' do 7 | @redises.lpush(@key, 20) 8 | @redises.lpush(@key, 10) 9 | 10 | expect(@redises.lindex(@key, 0)).to eq('10') 11 | expect(@redises.lindex(@key, 1)).to eq('20') 12 | end 13 | 14 | it 'treats negative indices as coming from the right' do 15 | @redises.lpush(@key, 20) 16 | @redises.lpush(@key, 10) 17 | 18 | expect(@redises.lindex(@key, -1)).to eq('20') 19 | expect(@redises.lindex(@key, -2)).to eq('10') 20 | end 21 | 22 | it 'gets an element from the list by its index when index is a string' do 23 | @redises.lpush(@key, 20) 24 | @redises.lpush(@key, 10) 25 | 26 | expect(@redises.lindex(@key, '0')).to eq('10') 27 | expect(@redises.lindex(@key, '1')).to eq('20') 28 | expect(@redises.lindex(@key, '-1')).to eq('20') 29 | expect(@redises.lindex(@key, '-2')).to eq('10') 30 | end 31 | 32 | it 'returns nil if the index is too large (and positive)' do 33 | @redises.lpush(@key, 20) 34 | 35 | expect(@redises.lindex(@key, 100)).to be_nil 36 | end 37 | 38 | it 'returns nil if the index is too large (and negative)' do 39 | @redises.lpush(@key, 20) 40 | 41 | expect(@redises.lindex(@key, -100)).to be_nil 42 | end 43 | 44 | it 'returns nil for nonexistent values' do 45 | expect(@redises.lindex(@key, 0)).to be_nil 46 | end 47 | 48 | it_should_behave_like 'a list-only command' 49 | end 50 | -------------------------------------------------------------------------------- /spec/commands/linsert_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#linsert(key, :before|:after, pivot, value)' do 4 | let(:default_error) { Redis::CommandError } 5 | 6 | before { @key = 'mock-redis-test:48733' } 7 | 8 | it 'returns the new size of the list when the pivot is found' do 9 | @redises.lpush(@key, 'X') 10 | 11 | expect(@redises.linsert(@key, :before, 'X', 'Y')).to eq(2) 12 | expect(@redises.lpushx(@key, 'X')).to eq(3) 13 | end 14 | 15 | it 'does nothing when run against a nonexistent key' do 16 | expect(@redises.linsert(@key, :before, 1, 2)).to eq(0) 17 | expect(@redises.get(@key)).to be_nil 18 | end 19 | 20 | it 'returns -1 if the pivot is not found' do 21 | @redises.lpush(@key, 1) 22 | expect(@redises.linsert(@key, :after, 'X', 'Y')).to eq(-1) 23 | end 24 | 25 | it 'inserts elements before the pivot when given :before as position' do 26 | @redises.lpush(@key, 'bert') 27 | @redises.linsert(@key, :before, 'bert', 'ernie') 28 | 29 | expect(@redises.lindex(@key, 0)).to eq('ernie') 30 | expect(@redises.lindex(@key, 1)).to eq('bert') 31 | end 32 | 33 | it "inserts elements before the pivot when given 'before' as position" do 34 | @redises.lpush(@key, 'bert') 35 | @redises.linsert(@key, 'before', 'bert', 'ernie') 36 | 37 | expect(@redises.lindex(@key, 0)).to eq('ernie') 38 | expect(@redises.lindex(@key, 1)).to eq('bert') 39 | end 40 | 41 | it 'inserts elements after the pivot when given :after as position' do 42 | @redises.lpush(@key, 'bert') 43 | @redises.linsert(@key, :after, 'bert', 'ernie') 44 | 45 | expect(@redises.lindex(@key, 0)).to eq('bert') 46 | expect(@redises.lindex(@key, 1)).to eq('ernie') 47 | end 48 | 49 | it "inserts elements after the pivot when given 'after' as position" do 50 | @redises.lpush(@key, 'bert') 51 | @redises.linsert(@key, 'after', 'bert', 'ernie') 52 | 53 | expect(@redises.lindex(@key, 0)).to eq('bert') 54 | expect(@redises.lindex(@key, 1)).to eq('ernie') 55 | end 56 | 57 | it 'raises an error when given a position that is neither before nor after' do 58 | expect do 59 | @redises.linsert(@key, :near, 1, 2) 60 | end.to raise_error(Redis::CommandError) 61 | end 62 | 63 | it 'stores values as strings' do 64 | @redises.lpush(@key, 1) 65 | @redises.linsert(@key, :before, 1, 2) 66 | expect(@redises.lindex(@key, 0)).to eq('2') 67 | end 68 | 69 | it_should_behave_like 'a list-only command' 70 | end 71 | -------------------------------------------------------------------------------- /spec/commands/llen_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#llen(key)' do 4 | before { @key = 'mock-redis-test:78407' } 5 | 6 | it 'returns 0 for a nonexistent key' do 7 | expect(@redises.llen(@key)).to eq(0) 8 | end 9 | 10 | it 'returns the length of the list' do 11 | 5.times { @redises.lpush(@key, 'X') } 12 | expect(@redises.llen(@key)).to eq(5) 13 | end 14 | 15 | it_should_behave_like 'a list-only command' 16 | end 17 | -------------------------------------------------------------------------------- /spec/commands/lmpop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lmpop(*keys)', redis: 7.0 do 4 | before do 5 | @list1 = 'mock-redis-test:lmpop-list' 6 | @list2 = 'mock-redis-test:lmpop-list2' 7 | 8 | @redises.lpush(@list1, 'c') 9 | @redises.lpush(@list1, 'b') 10 | @redises.lpush(@list1, 'a') 11 | 12 | @redises.lpush(@list2, 'z') 13 | @redises.lpush(@list2, 'y') 14 | @redises.lpush(@list2, 'x') 15 | end 16 | 17 | it 'returns and removes the first element of the first non-empty list' do 18 | expect(@redises.lmpop('empty', @list1, @list2)).to eq([@list1, ['a']]) 19 | 20 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b c]) 21 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) 22 | end 23 | 24 | it 'returns and removes the first element of the first non-empty list when modifier is LEFT' do 25 | expect(@redises.lmpop('empty', @list1, @list2, modifier: 'LEFT')).to eq([@list1, ['a']]) 26 | 27 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b c]) 28 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) 29 | end 30 | 31 | it 'returns and removes the last element of the first non-empty list when modifier is RIGHT' do 32 | expect(@redises.lmpop('empty', @list1, @list2, modifier: 'RIGHT')).to eq([@list1, ['c']]) 33 | 34 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b]) 35 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) 36 | end 37 | 38 | it 'returns and removes multiple elements from the front when count is given' do 39 | expect(@redises.lmpop('empty', @list1, @list2, count: 2)).to eq([@list1, %w[a b]]) 40 | 41 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[c]) 42 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) 43 | end 44 | 45 | it 'returns and removes multiple elements from the back when count given and modifier is RIGHT' do 46 | expect(@redises.lmpop('empty', @list1, @list2, count: 2, modifier: 'RIGHT')).to( 47 | eq([@list1, %w[c b]]) 48 | ) 49 | 50 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a]) 51 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) 52 | end 53 | 54 | it 'returns falsed if all lists are empty' do 55 | expect(@redises.lmpop('empty')).to be_nil 56 | 57 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b c]) 58 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) 59 | end 60 | 61 | it 'removes empty lists' do 62 | (@redises.llen(@list1) + @redises.llen(@list2)).times { @redises.lmpop(@list1, @list2) } 63 | expect(@redises.get(@list1)).to be_nil 64 | end 65 | 66 | it 'raises an error for non-list source value' do 67 | @redises.set(@list1, 'string value') 68 | 69 | expect do 70 | @redises.lmpop(@list1, @list2) 71 | end.to raise_error(Redis::CommandError) 72 | end 73 | 74 | it_should_behave_like 'a list-only command' 75 | end 76 | -------------------------------------------------------------------------------- /spec/commands/lpop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lpop(key)' do 4 | before { @key = 'mock-redis-test:65374' } 5 | 6 | it 'returns and removes the first element of a list' do 7 | @redises.lpush(@key, 1) 8 | @redises.lpush(@key, 2) 9 | 10 | expect(@redises.lpop(@key)).to eq('2') 11 | 12 | expect(@redises.llen(@key)).to eq(1) 13 | end 14 | 15 | it 'returns nil if the list is empty' do 16 | @redises.lpush(@key, 'foo') 17 | @redises.lpop(@key) 18 | 19 | expect(@redises.lpop(@key)).to be_nil 20 | end 21 | 22 | it 'returns nil for nonexistent values' do 23 | expect(@redises.lpop(@key)).to be_nil 24 | end 25 | 26 | it 'removes empty lists' do 27 | @redises.lpush(@key, 'foo') 28 | @redises.lpop(@key) 29 | 30 | expect(@redises.get(@key)).to be_nil 31 | end 32 | 33 | context 'requesting multiple records to be popped in the 3rd arg' do 34 | let(:count) { 10 } 35 | 36 | before { count.times { @redises.lpush(@key, _1) } } 37 | 38 | it 'returns the number of records requested' do 39 | expect(@redises.lpop(@key, count)).to eq((count - 1).downto(0).map(&:to_s)) 40 | end 41 | 42 | context 'when there are no records' do 43 | let(:count) { 0 } 44 | it 'returns nil' do 45 | expect(@redises.lpop(@key, 10)).to eq(nil) 46 | end 47 | end 48 | 49 | context 'when there are more records than requested' do 50 | let(:count) { 20 } 51 | 52 | it 'returns the most recent 10 records' do 53 | expect(@redises.lpop(@key, 10)).to eq((count - 1).downto(10).map(&:to_s)) 54 | end 55 | end 56 | 57 | context 'when requesting more records than exist' do 58 | it 'should return only the valid records in the bucket' do 59 | expect(@redises.lpop(@key, 1_000_000)).to eq((count - 1).downto(0).map(&:to_s)) 60 | end 61 | end 62 | end 63 | 64 | it_should_behave_like 'a list-only command' 65 | end 66 | -------------------------------------------------------------------------------- /spec/commands/lpush_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lpush(key, value)' do 4 | before { @key = 'mock-redis-test:57367' } 5 | 6 | it 'returns the new size of the list' do 7 | expect(@redises.lpush(@key, 'X')).to eq(1) 8 | expect(@redises.lpush(@key, 'X')).to eq(2) 9 | end 10 | 11 | it 'creates a new list when run against a nonexistent key' do 12 | @redises.lpush(@key, 'value') 13 | expect(@redises.llen(@key)).to eq(1) 14 | end 15 | 16 | it 'prepends items to the list' do 17 | @redises.lpush(@key, 'bert') 18 | @redises.lpush(@key, 'ernie') 19 | 20 | expect(@redises.lindex(@key, 0)).to eq('ernie') 21 | expect(@redises.lindex(@key, 1)).to eq('bert') 22 | end 23 | 24 | it 'stores values as strings' do 25 | @redises.lpush(@key, 1) 26 | expect(@redises.lindex(@key, 0)).to eq('1') 27 | end 28 | 29 | it 'supports a variable number of arguments' do 30 | expect(@redises.lpush(@key, [1, 2, 3])).to eq(3) 31 | expect(@redises.lindex(@key, 0)).to eq('3') 32 | expect(@redises.lindex(@key, 1)).to eq('2') 33 | expect(@redises.lindex(@key, 2)).to eq('1') 34 | end 35 | 36 | it 'raises an error if an empty array is given' do 37 | expect do 38 | @redises.lpush(@key, []) 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | 42 | it_should_behave_like 'a list-only command' 43 | end 44 | -------------------------------------------------------------------------------- /spec/commands/lpushx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lpushx(key, value)' do 4 | before { @key = 'mock-redis-test:81267' } 5 | 6 | it 'returns the new size of the list' do 7 | @redises.lpush(@key, 'X') 8 | 9 | expect(@redises.lpushx(@key, 'X')).to eq(2) 10 | expect(@redises.lpushx(@key, 'X')).to eq(3) 11 | end 12 | 13 | it 'does nothing when run against a nonexistent key' do 14 | @redises.lpushx(@key, 'value') 15 | expect(@redises.get(@key)).to be_nil 16 | end 17 | 18 | it 'prepends items to the list' do 19 | @redises.lpush(@key, 'bert') 20 | @redises.lpushx(@key, 'ernie') 21 | 22 | expect(@redises.lindex(@key, 0)).to eq('ernie') 23 | expect(@redises.lindex(@key, 1)).to eq('bert') 24 | end 25 | 26 | it 'stores values as strings' do 27 | @redises.lpush(@key, 1) 28 | @redises.lpushx(@key, 2) 29 | expect(@redises.lindex(@key, 0)).to eq('2') 30 | end 31 | 32 | it 'raises an error if an empty array is given' do 33 | @redises.lpush(@key, 'X') 34 | expect do 35 | @redises.lpushx(@key, []) 36 | end.to raise_error(Redis::CommandError) 37 | end 38 | 39 | it 'stores multiple items if an array of more than one item is given' do 40 | @redises.lpush(@key, 'X') 41 | expect(@redises.lpushx(@key, [1, 2])).to eq(3) 42 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[2 1 X]) 43 | end 44 | 45 | it_should_behave_like 'a list-only command' 46 | end 47 | -------------------------------------------------------------------------------- /spec/commands/lrange_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lrange(key, start, stop)' do 4 | before do 5 | @key = 'mock-redis-test:68036' 6 | 7 | @redises.lpush(@key, 'v4') 8 | @redises.lpush(@key, 'v3') 9 | @redises.lpush(@key, 'v2') 10 | @redises.lpush(@key, 'v1') 11 | @redises.lpush(@key, 'v0') 12 | end 13 | 14 | it 'returns a subset of the list inclusive of the right end' do 15 | expect(@redises.lrange(@key, 0, 2)).to eq(%w[v0 v1 v2]) 16 | end 17 | 18 | it 'returns a subset of the list when start and end are strings' do 19 | expect(@redises.lrange(@key, '0', '2')).to eq(%w[v0 v1 v2]) 20 | end 21 | 22 | it 'returns an empty list when start > end' do 23 | expect(@redises.lrange(@key, 3, 2)).to eq([]) 24 | end 25 | 26 | it 'works with a negative stop index' do 27 | expect(@redises.lrange(@key, 2, -1)).to eq(%w[v2 v3 v4]) 28 | end 29 | 30 | it 'works with negative start and stop indices' do 31 | expect(@redises.lrange(@key, -2, -1)).to eq(%w[v3 v4]) 32 | end 33 | 34 | it 'works with negative start indices less than list length' do 35 | expect(@redises.lrange(@key, -10, -2)).to eq(%w[v0 v1 v2 v3]) 36 | end 37 | 38 | it 'returns [] when run against a nonexistent value' do 39 | expect(@redises.lrange('mock-redis-test:bogus-key', 0, 1)).to eq([]) 40 | end 41 | 42 | it 'returns [] when start is too large' do 43 | expect(@redises.lrange(@key, 100, 100)).to eq([]) 44 | end 45 | 46 | it 'finds the end of the list correctly when end is too large' do 47 | expect(@redises.lrange(@key, 4, 10)).to eq(%w[v4]) 48 | end 49 | 50 | it_should_behave_like 'a list-only command' 51 | end 52 | -------------------------------------------------------------------------------- /spec/commands/lrem_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lrem(key, count, value)' do 4 | before do 5 | @key = 'mock-redis-test:66767' 6 | 7 | %w[99 bottles of beer on the wall 8 | 99 bottles of beer 9 | take one down 10 | pass it around 11 | 98 bottles of beer on the wall].reverse_each do |x| 12 | @redises.lpush(@key, x) 13 | end 14 | end 15 | 16 | it 'deletes the first count instances of key when count > 0' do 17 | @redises.lrem(@key, 2, 'bottles') 18 | 19 | expect(@redises.lrange(@key, 0, 8)).to eq( 20 | %w[ 21 | 99 of beer on the wall 22 | 99 of beer 23 | ] 24 | ) 25 | 26 | expect(@redises.lrange(@key, -7, -1)).to eq(%w[98 bottles of beer on the wall]) 27 | end 28 | 29 | it 'deletes the last count instances of key when count < 0' do 30 | @redises.lrem(@key, -2, 'bottles') 31 | 32 | expect(@redises.lrange(@key, 0, 9)).to eq( 33 | %w[ 34 | 99 bottles of beer on the wall 35 | 99 of beer 36 | ] 37 | ) 38 | 39 | expect(@redises.lrange(@key, -6, -1)).to eq(%w[98 of beer on the wall]) 40 | end 41 | 42 | it 'deletes all instances of key when count == 0' do 43 | @redises.lrem(@key, 0, 'bottles') 44 | expect(@redises.lrange(@key, 0, -1).grep(/bottles/)).to be_empty 45 | end 46 | 47 | it 'returns the number of elements deleted' do 48 | expect(@redises.lrem(@key, 2, 'bottles')).to eq(2) 49 | end 50 | 51 | it 'returns the number of elements deleted even if you ask for more' do 52 | expect(@redises.lrem(@key, 10, 'bottles')).to eq(3) 53 | end 54 | 55 | it 'stringifies value' do 56 | expect(@redises.lrem(@key, 0, 99)).to eq(2) 57 | end 58 | 59 | it 'returns 0 when run against a nonexistent value' do 60 | expect(@redises.lrem('mock-redis-test:bogus-key', 0, 1)).to eq(0) 61 | end 62 | 63 | it 'returns 0 when run against an empty list' do 64 | @redises.llen(@key).times { @redises.lpop(@key) } # empty the list 65 | expect(@redises.lrem(@key, 0, 'beer')).to eq(0) 66 | end 67 | 68 | it 'raises an error if the value does not look like an integer' do 69 | expect do 70 | @redises.lrem(@key, 'foo', 'bottles') 71 | end.to raise_error(ArgumentError) 72 | end 73 | 74 | it 'removes empty lists' do 75 | other_key = "mock-redis-test:lrem-#{__LINE__}" 76 | 77 | @redises.lpush(other_key, 'foo') 78 | @redises.lrem(other_key, 0, 'foo') 79 | 80 | expect(@redises.get(other_key)).to be_nil 81 | end 82 | 83 | it_should_behave_like 'a list-only command' 84 | end 85 | -------------------------------------------------------------------------------- /spec/commands/lset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#lset(key, index, value)' do 4 | before do 5 | @key = 'mock-redis-test:21522' 6 | 7 | @redises.lpush(@key, 'v1') 8 | @redises.lpush(@key, 'v0') 9 | end 10 | 11 | it "returns 'OK'" do 12 | expect(@redises.lset(@key, 0, 'newthing')).to eq('OK') 13 | end 14 | 15 | it "sets the list's value at index to value" do 16 | @redises.lset(@key, 0, 'newthing') 17 | expect(@redises.lindex(@key, 0)).to eq('newthing') 18 | end 19 | 20 | it "sets the list's value at index to value when the index is a string" do 21 | @redises.lset(@key, '0', 'newthing') 22 | expect(@redises.lindex(@key, 0)).to eq('newthing') 23 | end 24 | 25 | it 'stringifies value' do 26 | @redises.lset(@key, 0, 12_345) 27 | expect(@redises.lindex(@key, 0)).to eq('12345') 28 | end 29 | 30 | it 'raises an exception for nonexistent keys' do 31 | expect do 32 | @redises.lset('mock-redis-test:bogus-key', 100, 'value') 33 | end.to raise_error(Redis::CommandError) 34 | end 35 | 36 | it 'raises an exception for out-of-range indices' do 37 | expect do 38 | @redises.lset(@key, 100, 'value') 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | 42 | it_should_behave_like 'a list-only command' 43 | end 44 | -------------------------------------------------------------------------------- /spec/commands/ltrim_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#ltrim(key, start, stop)' do 4 | before do 5 | @key = 'mock-redis-test:22310' 6 | 7 | %w[v0 v1 v2 v3 v4].reverse_each { |v| @redises.lpush(@key, v) } 8 | end 9 | 10 | it "returns 'OK'" do 11 | expect(@redises.ltrim(@key, 1, 3)).to eq('OK') 12 | end 13 | 14 | it 'trims the list to include only the specified elements' do 15 | @redises.ltrim(@key, 1, 3) 16 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[v1 v2 v3]) 17 | end 18 | 19 | it 'trims the list when start and stop are strings' do 20 | @redises.ltrim(@key, '1', '3') 21 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[v1 v2 v3]) 22 | end 23 | 24 | it 'trims the list to include only the specified elements (negative indices)' do 25 | @redises.ltrim(@key, -2, -1) 26 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[v3 v4]) 27 | end 28 | 29 | it 'trims the list to include only the specified elements (out of range negative indices)' do 30 | @redises.ltrim(@key, -10, -2) 31 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[v0 v1 v2 v3]) 32 | end 33 | 34 | it 'does not crash on overly-large indices' do 35 | @redises.ltrim(@key, 100, 200) 36 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[]) 37 | end 38 | 39 | it 'removes empty lists' do 40 | @redises.ltrim(@key, 1, 0) 41 | expect(@redises.get(@key)).to be_nil 42 | end 43 | 44 | it_should_behave_like 'a list-only command' 45 | end 46 | -------------------------------------------------------------------------------- /spec/commands/mapped_hmget_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mapped_hmget(key, *fields)' do 4 | before do 5 | @key = 'mock-redis-test:mapped_hmget' 6 | @redises.hmset(@key, 'k1', 'v1', 'k2', 'v2') 7 | end 8 | 9 | it 'returns values stored at key' do 10 | expect(@redises.mapped_hmget(@key, 'k1', 'k2')).to eq({ 'k1' => 'v1', 'k2' => 'v2' }) 11 | end 12 | 13 | it 'returns nils for missing fields' do 14 | expect(@redises.mapped_hmget(@key, 'k1', 'mock-redis-test:nonesuch')). 15 | to eq({ 'k1' => 'v1', 'mock-redis-test:nonesuch' => nil }) 16 | end 17 | 18 | it 'treats an array as multiple keys' do 19 | expect(@redises.mapped_hmget(@key, %w[k1 k2])).to eq({ 'k1' => 'v1', 'k2' => 'v2' }) 20 | end 21 | 22 | it 'raises an error if given no fields' do 23 | expect do 24 | @redises.mapped_hmget(@key) 25 | end.to raise_error(Redis::CommandError) 26 | end 27 | 28 | it_should_behave_like 'a hash-only command' 29 | end 30 | -------------------------------------------------------------------------------- /spec/commands/mapped_hmset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mapped_hmset(key, hash={})' do 4 | before do 5 | @key = 'mock-redis-test:mapped_hmset' 6 | end 7 | 8 | it "returns 'OK'" do 9 | expect(@redises.mapped_hmset(@key, 'k1' => 'v1', 'k2' => 'v2')).to eq('OK') 10 | end 11 | 12 | it 'sets the values' do 13 | @redises.mapped_hmset(@key, 'k1' => 'v1', 'k2' => 'v2') 14 | expect(@redises.hmget(@key, 'k1', 'k2')).to eq(%w[v1 v2]) 15 | end 16 | 17 | it 'updates an existing hash' do 18 | @redises.hmset(@key, 'foo', 'bar') 19 | @redises.mapped_hmset(@key, 'bert' => 'ernie', 'diet' => 'coke') 20 | 21 | expect(@redises.hmget(@key, 'foo', 'bert', 'diet')). 22 | to eq(%w[bar ernie coke]) 23 | end 24 | 25 | it 'stores the values as strings' do 26 | @redises.mapped_hmset(@key, 'one' => 1) 27 | expect(@redises.hget(@key, 'one')).to eq('1') 28 | end 29 | 30 | it 'raises an error if given no hash' do 31 | expect do 32 | @redises.mapped_hmset(@key) 33 | end.to raise_error(ArgumentError) 34 | end 35 | 36 | it 'raises an error if given a an odd length array' do 37 | expect do 38 | @redises.mapped_hmset(@key, [1]) 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | 42 | it 'raises an error if given a non-hash value' do 43 | expect do 44 | @redises.mapped_hmset(@key, 1) 45 | end.to raise_error(NoMethodError) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/commands/mapped_mget_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mapped_mget(*keys)' do 4 | before do 5 | @key1 = 'mock-redis-test:a' 6 | @key2 = 'mock-redis-test:b' 7 | @key3 = 'mock-redis-test:c' 8 | 9 | @redises.set(@key1, '1') 10 | @redises.set(@key2, '2') 11 | end 12 | 13 | it 'returns a hash' do 14 | expect(@redises.mapped_mget(@key1, @key2, @key3)).to eq( 15 | @key1 => '1', 16 | @key2 => '2', 17 | @key3 => nil 18 | ) 19 | end 20 | 21 | it 'returns a hash even when no matches' do 22 | expect(@redises.mapped_mget('qwer')).to eq('qwer' => nil) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/commands/mapped_mset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mapped_mset(hash)' do 4 | before do 5 | @key1 = 'mock-redis-test:a' 6 | @key2 = 'mock-redis-test:b' 7 | @key3 = 'mock-redis-test:c' 8 | 9 | @redises.set(@key1, '1') 10 | @redises.set(@key2, '2') 11 | end 12 | 13 | it 'sets the values properly' do 14 | expect(@redises.mapped_mset(@key1 => 'one', @key3 => 'three')).to eq('OK') 15 | expect(@redises.get(@key1)).to eq('one') 16 | expect(@redises.get(@key2)).to eq('2') # left alone 17 | expect(@redises.get(@key3)).to eq('three') 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/mapped_msetnx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mapped_msetnx(hash)' do 4 | before do 5 | @key1 = 'mock-redis-test:a' 6 | @key2 = 'mock-redis-test:b' 7 | @key3 = 'mock-redis-test:c' 8 | 9 | @redises.set(@key1, '1') 10 | @redises.set(@key2, '2') 11 | end 12 | 13 | it 'sets properly when none collide' do 14 | expect(@redises.mapped_msetnx(@key3 => 'three')).to eq(true) 15 | expect(@redises.get(@key1)).to eq('1') # existed; untouched 16 | expect(@redises.get(@key2)).to eq('2') # existed; untouched 17 | expect(@redises.get(@key3)).to eq('three') 18 | end 19 | 20 | it 'does not set any when any collide' do 21 | expect(@redises.mapped_msetnx(@key1 => 'one', @key3 => 'three')).to eq(false) 22 | expect(@redises.get(@key1)).to eq('1') # existed; untouched 23 | expect(@redises.get(@key2)).to eq('2') # existed; untouched 24 | expect(@redises.get(@key3)).to be_nil 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/commands/memory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#memory usage [mock only]' do 4 | it 'only handles the usage subcommand' do 5 | expect { @redises.mock.call(%w[memory stats]) }.to raise_error(ArgumentError) 6 | end 7 | 8 | context 'when the key does not exist' do 9 | before { @redises.real.del('foo') } 10 | 11 | it 'returns nil' do 12 | expect(@redises.call(%w[memory usage foo])).to be_nil 13 | end 14 | end 15 | 16 | context 'when the key does exist' do 17 | before { @redises.set('foo', 'a' * 100) } 18 | 19 | it 'returns the memory usage' do 20 | expect(@redises.call(%w[memory usage foo])).to be_a(Integer) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/commands/mget_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mget(key [, key, ...])' do 4 | before do 5 | @key1 = 'mock-redis-test:mget1' 6 | @key2 = 'mock-redis-test:mget2' 7 | 8 | @redises.set(@key1, 1) 9 | @redises.set(@key2, 2) 10 | end 11 | 12 | context 'emulate param array' do 13 | it 'returns an array of values' do 14 | expect(@redises.mget([@key1, @key2])).to eq(%w[1 2]) 15 | end 16 | 17 | it 'returns an array of values' do 18 | expect(@redises.mget([@key1, @key2])).to eq(%w[1 2]) 19 | end 20 | 21 | it 'returns nil for non-string keys' do 22 | list = 'mock-redis-test:mget-list' 23 | 24 | @redises.lpush(list, 'bork bork bork') 25 | 26 | expect(@redises.mget([@key1, @key2, list])).to eq(['1', '2', nil]) 27 | end 28 | end 29 | 30 | context 'emulate params strings' do 31 | it 'returns an array of values' do 32 | expect(@redises.mget(@key1, @key2)).to eq(%w[1 2]) 33 | end 34 | 35 | it 'returns nil for missing keys' do 36 | expect(@redises.mget(@key1, 'mock-redis-test:not-found', @key2)).to eq(['1', nil, '2']) 37 | end 38 | 39 | it 'returns nil for non-string keys' do 40 | list = 'mock-redis-test:mget-list' 41 | 42 | @redises.lpush(list, 'bork bork bork') 43 | 44 | expect(@redises.mget(@key1, @key2, list)).to eq(['1', '2', nil]) 45 | end 46 | 47 | it 'raises an error if you pass it 0 arguments' do 48 | expect do 49 | @redises.mget 50 | end.to raise_error(Redis::CommandError) 51 | end 52 | 53 | it 'raises an error if you pass it empty array' do 54 | expect do 55 | @redises.mget([]) 56 | end.to raise_error(Redis::CommandError) 57 | end 58 | end 59 | 60 | context 'emulate block' do 61 | it 'returns an array of values' do 62 | expect(@redises.mget(@key1, @key2) { |values| values.map(&:to_i) }).to eq([1, 2]) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/commands/mset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#mset(key, value [, key, value, ...])' do 4 | before do 5 | @key1 = 'mock-redis-test:mset1' 6 | @key2 = 'mock-redis-test:mset2' 7 | end 8 | 9 | it "responds with 'OK'" do 10 | expect(@redises.mset(@key1, 1)).to eq('OK') 11 | end 12 | 13 | it "responds with 'OK' for passed Array with 1 item" do 14 | expect(@redises.mset([@key1, 1])).to eq('OK') 15 | end 16 | 17 | it "responds with 'OK' for passed Array with 2 items" do 18 | expect(@redises.mset([@key1, 1, @key2, 2])).to eq('OK') 19 | end 20 | 21 | it 'sets the values' do 22 | @redises.mset(@key1, 'value1', @key2, 'value2') 23 | expect(@redises.mget(@key1, @key2)).to eq(%w[value1 value2]) 24 | end 25 | 26 | it 'raises an error if given an odd number of arguments' do 27 | expect do 28 | @redises.mset(@key1, 'value1', @key2) 29 | end.to raise_error(Redis::CommandError) 30 | end 31 | 32 | it 'raises an error if given 0 arguments' do 33 | expect do 34 | @redises.mset 35 | end.to raise_error(Redis::CommandError) 36 | end 37 | 38 | it 'raises an error if given odd-sized array' do 39 | expect do 40 | @redises.mset([@key1, 1, @key2]) 41 | end.to raise_error(Redis::CommandError) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/commands/msetnx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#msetnx(key, value [, key, value, ...])' do 4 | before do 5 | @key1 = 'mock-redis-test:msetnx1' 6 | @key2 = 'mock-redis-test:msetnx2' 7 | end 8 | 9 | it 'responds with 1 if any keys were set' do 10 | expect(@redises.msetnx(@key1, 1)).to eq(true) 11 | end 12 | 13 | it 'sets the values' do 14 | @redises.msetnx(@key1, 'value1', @key2, 'value2') 15 | expect(@redises.mget(@key1, @key2)).to eq(%w[value1 value2]) 16 | end 17 | 18 | it 'does nothing if any value is already set' do 19 | @redises.set(@key1, 'old1') 20 | @redises.msetnx(@key1, 'value1', @key2, 'value2') 21 | expect(@redises.mget(@key1, @key2)).to eq(['old1', nil]) 22 | end 23 | 24 | it 'responds with 0 if any value is already set' do 25 | @redises.set(@key1, 'old1') 26 | expect(@redises.msetnx(@key1, 'value1', @key2, 'value2')).to eq(false) 27 | end 28 | 29 | it 'raises an error if given an odd number of arguments' do 30 | expect do 31 | @redises.msetnx(@key1, 'value1', @key2) 32 | end.to raise_error(Redis::CommandError) 33 | end 34 | 35 | it 'raises an error if given 0 arguments' do 36 | expect do 37 | @redises.msetnx 38 | end.to raise_error(Redis::CommandError) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/commands/persist_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#persist(key)' do 4 | before do 5 | @key = 'mock-redis-test:persist' 6 | @redises.set(@key, 'spork') 7 | end 8 | 9 | it 'returns true for a key with a timeout' do 10 | @redises.expire(@key, 10_000) 11 | expect(@redises.persist(@key)).to eq(true) 12 | end 13 | 14 | it 'returns false for a key with no timeout' do 15 | expect(@redises.persist(@key)).to eq(false) 16 | end 17 | 18 | it 'returns false for a key that does not exist' do 19 | expect(@redises.persist('mock-redis-test:nonesuch')).to eq(false) 20 | end 21 | 22 | it 'removes the timeout' do 23 | @redises.expire(@key, 10_000) 24 | @redises.persist(@key) 25 | expect(@redises.persist(@key)).to eq(false) 26 | end 27 | 28 | context '[mock only]' do 29 | # These are mock-only since we can't actually manipulate time in 30 | # the real Redis. 31 | 32 | before(:all) do 33 | @mock = @redises.mock 34 | end 35 | 36 | before do 37 | @now = Time.now 38 | allow(Time).to receive(:now).and_return(@now) 39 | end 40 | 41 | it 'makes keys stay around' do 42 | @mock.expire(@key, 5) 43 | @mock.persist(@key) 44 | allow(Time).to receive(:now).and_return(@now + 5) 45 | expect(@mock.get(@key)).not_to be_nil 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/commands/pexpireat_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#pexpireat(key, timestamp_ms)' do 4 | before do 5 | @key = 'mock-redis-test:pexpireat' 6 | @redises.set(@key, 'spork') 7 | end 8 | 9 | def now_ms 10 | (Time.now.to_f * 1000).to_i 11 | end 12 | 13 | it 'returns true for a key that exists' do 14 | expect(@redises.pexpireat(@key, now_ms + 1)).to eq(true) 15 | end 16 | 17 | it 'returns false for a key that does not exist' do 18 | expect(@redises.pexpireat('mock-redis-test:nonesuch', 19 | now_ms + 1)).to eq(false) 20 | end 21 | 22 | it 'removes a key immediately when timestamp is now' do 23 | @redises.pexpireat(@key, now_ms) 24 | expect(@redises.get(@key)).to be_nil 25 | end 26 | 27 | it 'returns true when time object is provided' do 28 | expect(@redises.pexpireat(@key, Time.now)).to eq true 29 | end 30 | 31 | it 'works with options', redis: '7.0' do 32 | expect(@redises.expire(@key, now_ms + 20)).to eq(true) 33 | expect(@redises.expire(@key, now_ms + 10, lt: true)).to eq(true) 34 | expect(@redises.expire(@key, now_ms + 15, lt: true)).to eq(false) 35 | expect(@redises.expire(@key, now_ms + 20, gt: true)).to eq(true) 36 | expect(@redises.expire(@key, now_ms + 15, gt: true)).to eq(false) 37 | expect(@redises.expire(@key, now_ms + 10, xx: true)).to eq(true) 38 | expect(@redises.expire(@key, now_ms + 10, nx: true)).to eq(false) 39 | expect(@redises.persist(@key)).to eq(true) 40 | expect(@redises.expire(@key, now_ms + 10, xx: true)).to eq(false) 41 | expect(@redises.expire(@key, now_ms + 10, nx: true)).to eq(true) 42 | end 43 | 44 | context '[mock only]' do 45 | # These are mock-only since we can't actually manipulate time in 46 | # the real Redis. 47 | 48 | before(:all) do 49 | @mock = @redises.mock 50 | end 51 | 52 | before do 53 | @now = Time.now 54 | allow(Time).to receive(:now).and_return(@now) 55 | end 56 | 57 | it_should_behave_like 'raises on invalid expire command options', :pexpireat 58 | 59 | it 'removes keys after enough time has passed' do 60 | @mock.pexpireat(@key, (@now.to_f * 1000).to_i + 5) 61 | allow(Time).to receive(:now).and_return(@now + 0.006) 62 | expect(@mock.get(@key)).to be_nil 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/commands/ping_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#ping' do 4 | it 'returns "PONG" with no arguments' do 5 | expect(@redises.ping).to eq('PONG') 6 | end 7 | 8 | it 'returns the argument' do 9 | expect(@redises.ping('HELLO')).to eq('HELLO') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/commands/psetex_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#psetex(key, miliseconds, value)' do 4 | before { @key = 'mock-redis-test:setex' } 5 | 6 | it "responds with 'OK'" do 7 | expect(@redises.psetex(@key, 10, 'value')).to eq('OK') 8 | end 9 | 10 | it 'sets the value' do 11 | @redises.psetex(@key, 10_000, 'value') 12 | expect(@redises.get(@key)).to eq('value') 13 | end 14 | 15 | it 'sets the expiration time' do 16 | @redises.psetex(@key, 10_000, 'value') 17 | 18 | # no guarantee these are the same 19 | expect(@redises.real.ttl(@key)).to be > 0 20 | expect(@redises.mock.ttl(@key)).to be > 0 21 | end 22 | 23 | it 'converts time correctly' do 24 | @redises.psetex(@key, 10_000_000, 'value') 25 | 26 | expect(@redises.mock.ttl(@key)).to be > 9_000 27 | end 28 | 29 | context 'when expiration time is zero' do 30 | let(:message) { /ERR invalid expire time in psetex/ } 31 | 32 | it 'raises Redis::CommandError' do 33 | expect do 34 | @redises.psetex(@key, 0, 'value') 35 | end.to raise_error(Redis::CommandError, message) 36 | end 37 | end 38 | 39 | context 'when expiration time is negative' do 40 | let(:message) { /ERR invalid expire time in psetex/ } 41 | 42 | it 'raises Redis::CommandError' do 43 | expect do 44 | @redises.psetex(@key, -2, 'value') 45 | end.to raise_error(Redis::CommandError, message) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/commands/pttl_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#pttl(key)' do 4 | before do 5 | @key = 'mock-redis-test:persist' 6 | @redises.set(@key, 'spork') 7 | end 8 | 9 | it 'returns -1 for a key with no expiration' do 10 | expect(@redises.pttl(@key)).to eq(-1) 11 | end 12 | 13 | it 'returns -2 for a key that does not exist' do 14 | expect(@redises.pttl('mock-redis-test:nonesuch')).to eq(-2) 15 | end 16 | 17 | it 'stringifies key' do 18 | @redises.expire(@key, 8) 19 | # Don't check against Redis since millisecond timing differences are likely 20 | expect(@redises.send_without_checking(:pttl, @key.to_sym)).to be > 0 21 | end 22 | 23 | context '[mock only]' do 24 | # These are mock-only since we can't actually manipulate time in 25 | # the real Redis. 26 | 27 | before(:all) do 28 | @mock = @redises.mock 29 | end 30 | 31 | before do 32 | @now = Time.now.round 33 | allow(Time).to receive(:now).and_return(@now) 34 | end 35 | 36 | it "gives you the key's remaining lifespan in milliseconds" do 37 | @mock.expire(@key, 5) 38 | expect(@mock.pttl(@key)).to eq(5000) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/commands/quit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#quit' do 4 | it "responds with 'OK'" do 5 | expect(@redises.quit).to eq('OK') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/randomkey_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#randomkey [mock only]' do 4 | before { @mock = @redises.mock } 5 | 6 | it 'finds a random key' do 7 | @keys = ['mock-redis-test:1', 'mock-redis-test:2', 'mock-redis-test:3'] 8 | 9 | @keys.each do |k| 10 | @mock.set(k, 1) 11 | end 12 | 13 | expect(@keys).to include(@mock.randomkey) 14 | end 15 | 16 | it 'returns nil when there are no keys' do 17 | @mock.keys('*').each { |k| @mock.del(k) } 18 | expect(@mock.randomkey).to be_nil 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/commands/rename_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#rename(key, newkey)' do 4 | before do 5 | @key = 'mock-redis-test:rename:key' 6 | @newkey = 'mock-redis-test:rename:newkey' 7 | 8 | @redises.set(@key, 'oof') 9 | end 10 | 11 | it 'responds with "OK"' do 12 | expect(@redises.rename(@key, @newkey)).to eq('OK') 13 | end 14 | 15 | it 'moves the data' do 16 | @redises.rename(@key, @newkey) 17 | expect(@redises.get(@newkey)).to eq('oof') 18 | end 19 | 20 | it 'raises an error when the source key is nonexistant' do 21 | @redises.del(@key) 22 | expect do 23 | @redises.rename(@key, @newkey) 24 | end.to raise_error(Redis::CommandError) 25 | end 26 | 27 | it 'responds with "OK" when key == newkey' do 28 | expect(@redises.rename(@key, @key)).to eq('OK') 29 | end 30 | 31 | it 'overwrites any existing value at newkey' do 32 | @redises.set(@newkey, 'rab') 33 | @redises.rename(@key, @newkey) 34 | expect(@redises.get(@newkey)).to eq('oof') 35 | end 36 | 37 | it 'keeps expiration' do 38 | @redises.expire(@key, 1000) 39 | @redises.rename(@key, @newkey) 40 | expect(@redises.ttl(@newkey)).to be > 0 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/commands/renamenx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#renamenx(key, newkey)' do 4 | before do 5 | @key = 'mock-redis-test:renamenx:key' 6 | @newkey = 'mock-redis-test:renamenx:newkey' 7 | 8 | @redises.set(@key, 'oof') 9 | end 10 | 11 | it 'responds with true when newkey does not exist' do 12 | expect(@redises.renamenx(@key, @newkey)).to eq(true) 13 | end 14 | 15 | it 'responds with false when newkey exists' do 16 | @redises.set(@newkey, 'monkey') 17 | expect(@redises.renamenx(@key, @newkey)).to eq(false) 18 | end 19 | 20 | it 'moves the data' do 21 | @redises.renamenx(@key, @newkey) 22 | expect(@redises.get(@newkey)).to eq('oof') 23 | end 24 | 25 | it 'raises an error when the source key is nonexistant' do 26 | @redises.del(@key) 27 | expect do 28 | @redises.renamenx(@key, @newkey) 29 | end.to raise_error(Redis::CommandError) 30 | end 31 | 32 | it 'returns false when key == newkey' do 33 | expect(@redises.renamenx(@key, @key)).to eq(false) 34 | end 35 | 36 | it 'leaves any existing value at newkey alone' do 37 | @redises.set(@newkey, 'rab') 38 | @redises.renamenx(@key, @newkey) 39 | expect(@redises.get(@newkey)).to eq('rab') 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/commands/restore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#restore(key, ttl, value)' do 4 | before do 5 | @key = 'mock-redis-test:45794' 6 | @src = MockRedis.new 7 | @src.set(@key, '123') 8 | @dumped_value = @src.dump(@key) 9 | @dst = MockRedis.new 10 | @now = Time.now.round 11 | allow(Time).to receive(:now).and_return(@now) 12 | end 13 | 14 | it 'allows dump/restoring values between two redis instances' do 15 | expect(@dst.restore(@key, 0, @dumped_value)).to eq('OK') 16 | expect(@dst.get(@key)).to eq('123') 17 | expect(@dst.pttl(@key)).to eq(-1) 18 | end 19 | 20 | context 'when the key being restored to already exists' do 21 | before do 22 | @dst.set(@key, '456') 23 | end 24 | 25 | it 'raises an error by default' do 26 | expect { @dst.restore(@key, 0, @dumped_value) }.to raise_error(Redis::CommandError) 27 | expect(@dst.get(@key)).to eq('456') 28 | end 29 | 30 | it 'allows replacing the key if replace==true' do 31 | expect(@dst.restore(@key, 0, @dumped_value, replace: true)).to eq('OK') 32 | expect(@dst.get(@key)).to eq('123') 33 | end 34 | end 35 | 36 | it 'sets ttl in ms' do 37 | @dst.restore(@key, 500, @dumped_value) 38 | expect(@dst.pttl(@key)).to eq(500) 39 | end 40 | 41 | it 'can dump/restore more complex data types' do 42 | key = 'a_hash' 43 | @src.mapped_hmset(key, foo: 'bar') 44 | @dst.restore(key, 0, @src.dump(key)) 45 | expect(@dst.hgetall(key)).to eq('foo' => 'bar') 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/commands/rpop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#rpop(key)' do 4 | before { @key = 'mock-redis-test:43093' } 5 | 6 | it 'returns and removes the first element of a list' do 7 | @redises.lpush(@key, 1) 8 | @redises.lpush(@key, 2) 9 | 10 | expect(@redises.rpop(@key)).to eq('1') 11 | 12 | expect(@redises.llen(@key)).to eq(1) 13 | end 14 | 15 | it 'returns nil if the list is empty' do 16 | @redises.lpush(@key, 'foo') 17 | @redises.rpop(@key) 18 | 19 | expect(@redises.rpop(@key)).to be_nil 20 | end 21 | 22 | it 'returns nil for nonexistent values' do 23 | expect(@redises.rpop(@key)).to be_nil 24 | end 25 | 26 | it 'removes empty lists' do 27 | @redises.lpush(@key, 'foo') 28 | @redises.rpop(@key) 29 | 30 | expect(@redises.get(@key)).to be_nil 31 | end 32 | 33 | context 'when count != nil' do 34 | it 'returns array with one element if count == 1' do 35 | @redises.rpush(@key, %w[one two three four five]) 36 | 37 | expect(@redises.rpop(@key, 1)).to eq(%w[five]) 38 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[one two three four]) 39 | end 40 | 41 | it 'returns the number of records requested' do 42 | @redises.rpush(@key, %w[one two three four five]) 43 | 44 | expect(@redises.rpop(@key, 2)).to eq(%w[five four]) 45 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[one two three]) 46 | end 47 | 48 | it 'returns nil for nonexistent key' do 49 | expect(@redises.rpop(@key, 2)).to be_nil 50 | end 51 | 52 | it 'returns all records when requesting more than list length' do 53 | @redises.rpush(@key, %w[one two three]) 54 | 55 | expect(@redises.rpop(@key, 10)).to eq(%w[three two one]) 56 | expect(@redises.lrange(@key, 0, -1)).to eq([]) 57 | end 58 | end 59 | 60 | it_should_behave_like 'a list-only command' do 61 | let(:args) { [1] } 62 | let(:error) do 63 | [ 64 | Redis::CommandError, 65 | /WRONGTYPE Operation against a key holding the wrong kind of value/ 66 | ] 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/commands/rpoplpush_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#rpoplpush(source, destination)' do 4 | before do 5 | @list1 = 'mock-redis-test:rpoplpush-list1' 6 | @list2 = 'mock-redis-test:rpoplpush-list2' 7 | 8 | @redises.lpush(@list1, 'b') 9 | @redises.lpush(@list1, 'a') 10 | 11 | @redises.lpush(@list2, 'y') 12 | @redises.lpush(@list2, 'x') 13 | end 14 | 15 | it 'returns the value moved' do 16 | expect(@redises.rpoplpush(@list1, @list2)).to eq('b') 17 | end 18 | 19 | it "returns false and doesn't append if source empty" do 20 | expect(@redises.rpoplpush('empty', @list1)).to be_nil 21 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b]) 22 | end 23 | 24 | it 'takes the last element of destination and prepends it to source' do 25 | @redises.rpoplpush(@list1, @list2) 26 | 27 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a]) 28 | expect(@redises.lrange(@list2, 0, -1)).to eq(%w[b x y]) 29 | end 30 | 31 | it 'rotates a list when source and destination are the same' do 32 | @redises.rpoplpush(@list1, @list1) 33 | expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b a]) 34 | end 35 | 36 | it 'removes empty lists' do 37 | @redises.llen(@list1).times { @redises.rpoplpush(@list1, @list2) } 38 | expect(@redises.get(@list1)).to be_nil 39 | end 40 | 41 | it 'raises an error for non-list source value' do 42 | @redises.set(@list1, 'string value') 43 | 44 | expect do 45 | @redises.rpoplpush(@list1, @list2) 46 | end.to raise_error(Redis::CommandError) 47 | end 48 | 49 | it_should_behave_like 'a list-only command' 50 | end 51 | -------------------------------------------------------------------------------- /spec/commands/rpush_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#rpush(key)' do 4 | before { @key = "mock-redis-test:#{__FILE__}" } 5 | 6 | it 'returns the new size of the list' do 7 | expect(@redises.rpush(@key, 'X')).to eq(1) 8 | expect(@redises.rpush(@key, 'X')).to eq(2) 9 | end 10 | 11 | it 'creates a new list when run against a nonexistent key' do 12 | @redises.rpush(@key, 'value') 13 | expect(@redises.llen(@key)).to eq(1) 14 | end 15 | 16 | it 'appends items to the list' do 17 | @redises.rpush(@key, 'bert') 18 | @redises.rpush(@key, 'ernie') 19 | 20 | expect(@redises.lindex(@key, 0)).to eq('bert') 21 | expect(@redises.lindex(@key, 1)).to eq('ernie') 22 | end 23 | 24 | it 'stores values as strings' do 25 | @redises.rpush(@key, 1) 26 | expect(@redises.lindex(@key, 0)).to eq('1') 27 | end 28 | 29 | it 'supports a variable number of arguments' do 30 | expect(@redises.rpush(@key, [1, 2, 3])).to eq(3) 31 | expect(@redises.lindex(@key, 0)).to eq('1') 32 | expect(@redises.lindex(@key, 1)).to eq('2') 33 | expect(@redises.lindex(@key, 2)).to eq('3') 34 | end 35 | 36 | it 'raises an error if an empty array is given' do 37 | expect do 38 | @redises.rpush(@key, []) 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | 42 | it_should_behave_like 'a list-only command' 43 | end 44 | -------------------------------------------------------------------------------- /spec/commands/rpushx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#rpushx(key, value)' do 4 | before { @key = 'mock-redis-test:92925' } 5 | 6 | it 'returns the new size of the list' do 7 | @redises.lpush(@key, 'X') 8 | 9 | expect(@redises.rpushx(@key, 'X')).to eq(2) 10 | expect(@redises.rpushx(@key, 'X')).to eq(3) 11 | end 12 | 13 | it 'does nothing when run against a nonexistent key' do 14 | @redises.rpushx(@key, 'value') 15 | expect(@redises.get(@key)).to be_nil 16 | end 17 | 18 | it 'appends items to the list' do 19 | @redises.lpush(@key, 'bert') 20 | @redises.rpushx(@key, 'ernie') 21 | 22 | expect(@redises.lindex(@key, 0)).to eq('bert') 23 | expect(@redises.lindex(@key, 1)).to eq('ernie') 24 | end 25 | 26 | it 'stores values as strings' do 27 | @redises.rpush(@key, 1) 28 | @redises.rpushx(@key, 2) 29 | expect(@redises.lindex(@key, 1)).to eq('2') 30 | end 31 | 32 | it 'raises an error if an empty array is given' do 33 | @redises.lpush(@key, 'X') 34 | expect do 35 | @redises.rpushx(@key, []) 36 | end.to raise_error(Redis::CommandError) 37 | end 38 | 39 | it 'stores multiple items if an array of more than one item is given' do 40 | @redises.lpush(@key, 'X') 41 | expect(@redises.rpushx(@key, [1, 2])).to eq(3) 42 | expect(@redises.lrange(@key, 0, -1)).to eq(%w[X 1 2]) 43 | end 44 | 45 | it_should_behave_like 'a list-only command' 46 | end 47 | -------------------------------------------------------------------------------- /spec/commands/sadd_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sadd(key, member)' do 4 | before { @key = 'mock-redis-test:sadd' } 5 | 6 | context 'sadd' do 7 | it 'returns true if the set did not already contain member' do 8 | expect(@redises.sadd(@key, 1)).to eq(1) 9 | end 10 | 11 | it 'returns false if the set did already contain member' do 12 | @redises.sadd(@key, 1) 13 | expect(@redises.sadd(@key, 1)).to eq(0) 14 | end 15 | 16 | it 'adds member to the set' do 17 | @redises.sadd(@key, 1) 18 | @redises.sadd(@key, 2) 19 | expect(@redises.smembers(@key)).to eq(%w[2 1]) 20 | end 21 | end 22 | 23 | context 'sadd?' do 24 | it 'returns true if the set did not already contain member' do 25 | expect(@redises.sadd?(@key, 1)).to eq(true) 26 | end 27 | 28 | it 'returns false if the set did already contain member' do 29 | @redises.sadd(@key, 1) 30 | expect(@redises.sadd?(@key, 1)).to eq(false) 31 | end 32 | 33 | it 'adds member to the set' do 34 | @redises.sadd?(@key, 1) 35 | @redises.sadd?(@key, 2) 36 | expect(@redises.smembers(@key)).to eq(%w[2 1]) 37 | end 38 | end 39 | 40 | describe 'adding multiple members at once' do 41 | it 'returns the amount of added members' do 42 | expect(@redises.sadd(@key, [1, 2, 3])).to eq(3) 43 | expect(@redises.sadd(@key, [1, 2, 3, 4])).to eq(1) 44 | end 45 | 46 | it 'returns 0 if the set did already contain all members' do 47 | @redises.sadd(@key, [1, 2, 3]) 48 | expect(@redises.sadd(@key, [1, 2, 3])).to eq(0) 49 | end 50 | 51 | it 'adds the members to the set' do 52 | @redises.sadd(@key, [1, 2, 3]) 53 | expect(@redises.smembers(@key)).to eq(%w[1 2 3]) 54 | end 55 | 56 | it 'adds an Array as a stringified member' do 57 | @redises.sadd(@key, [[1], 2, 3]) 58 | expect(@redises.smembers(@key)).to eq(%w[1 2 3]) 59 | end 60 | 61 | it 'raises an error if an empty array is given' do 62 | expect do 63 | @redises.sadd(@key, []) 64 | end.to raise_error(Redis::CommandError) 65 | end 66 | end 67 | 68 | it_should_behave_like 'a set-only command' 69 | end 70 | -------------------------------------------------------------------------------- /spec/commands/save_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#save' do 4 | it "responds with 'OK'" do 5 | expect(@redises.save).to eq('OK') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/scan_each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#scan_each' do 4 | subject { MockRedis::Database.new(self) } 5 | 6 | let(:match) { '*' } 7 | 8 | before do 9 | allow(subject).to receive(:data).and_return(data) 10 | end 11 | 12 | context 'when no keys are found' do 13 | let(:data) { {} } 14 | 15 | it 'does not iterate over any elements' do 16 | expect(subject.scan_each.to_a).to be_empty 17 | end 18 | end 19 | 20 | context 'when keys are found' do 21 | context 'when no match filter is supplied' do 22 | let(:data) { Array.new(20) { |i| "mock:key#{i}" }.to_h { |e| [e, nil] } } 23 | 24 | it 'iterates over each item in the collection' do 25 | expect(subject.scan_each.to_a).to match_array(data.keys) 26 | end 27 | end 28 | 29 | context 'when giving a custom match filter' do 30 | let(:match) { 'mock:key*' } 31 | let(:data) { ['mock:key', 'mock:key2', 'mock:otherkey'].to_h { |e| [e, nil] } } 32 | let(:expected) { %w[mock:key mock:key2] } 33 | 34 | it 'iterates over each item in the filtered collection' do 35 | expect(subject.scan_each(match: match).to_a).to match_array(expected) 36 | end 37 | end 38 | 39 | context 'when giving a custom match filter with a hash tag' do 40 | let(:match) { 'mock:key:{1}:*' } 41 | let(:data) { ['mock:key:{1}:1', 'mock:key:{1}:2', 'mock:key:{2}:1'].to_h { |e| [e, nil] } } 42 | let(:expected) { %w[mock:key:{1}:1 mock:key:{1}:2] } 43 | 44 | it 'returns a 0 cursor and the filtered collection' do 45 | expect(subject.scan_each(match: match)).to match_array(expected) 46 | end 47 | end 48 | 49 | context 'when giving a custom match and type filter' do 50 | let(:data) do 51 | { 'mock:stringkey' => 'mockvalue', 52 | 'mock:listkey' => ['mockvalue1'], 53 | 'mock:hashkey' => { 'mocksubkey' => 'mockvalue' }, 54 | 'mock:setkey' => Set.new(['mockvalue']), 55 | 'mock:zsetkey' => MockRedis::Zset.new(['mockvalue']) } 56 | end 57 | let(:match) { 'mock:*' } 58 | let(:type) { 'string' } 59 | let(:expected) { %w[mock:stringkey] } 60 | 61 | it 'iterates over each item in the filtered collection' do 62 | expect(subject.scan_each(match: match, type: type)).to match_array(expected) 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/commands/scard_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#scard(key)' do 4 | before { @key = 'mock-redis-test:scard' } 5 | 6 | it 'returns 0 for an empty set' do 7 | expect(@redises.scard(@key)).to eq(0) 8 | end 9 | 10 | it 'returns the number of elements in the set' do 11 | @redises.sadd(@key, 'one') 12 | @redises.sadd(@key, 'two') 13 | @redises.sadd(@key, 'three') 14 | expect(@redises.scard(@key)).to eq(3) 15 | end 16 | 17 | it_should_behave_like 'a set-only command' 18 | end 19 | -------------------------------------------------------------------------------- /spec/commands/script_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#script(subcommand, *args)' do 4 | before { @key = 'mock-redis-test:script' } 5 | 6 | it 'works with load subcommand' do 7 | expect { @redises.send_without_checking(:script, :load, 'return 1') }.to_not raise_error 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/commands/sdiff_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sdiff(key [, key, ...])' do 4 | before do 5 | @numbers = 'mock-redis-test:sdiff:numbers' 6 | @evens = 'mock-redis-test:sdiff:odds' 7 | @primes = 'mock-redis-test:sdiff:primes' 8 | 9 | (1..10).each { |i| @redises.sadd(@numbers, i) } 10 | [2, 4, 6, 8, 10].each { |i| @redises.sadd(@evens, i) } 11 | [2, 3, 5, 7].each { |i| @redises.sadd(@primes, i) } 12 | end 13 | 14 | it 'returns the first set minus the second set' do 15 | expect(@redises.sdiff(@numbers, @evens)).to eq(%w[1 3 5 7 9]) 16 | end 17 | 18 | it 'returns the first set minus all the other sets' do 19 | expect(@redises.sdiff(@numbers, @evens, @primes)).to eq(%w[1 9]) 20 | end 21 | 22 | it 'treats missing keys as empty sets' do 23 | expect(@redises.sdiff(@evens, 'mock-redis-test:nonesuch')).to eq(%w[2 4 6 8 10]) 24 | end 25 | 26 | it 'returns the first set when called with a single argument' do 27 | expect(@redises.sdiff(@primes)).to eq(%w[2 3 5 7]) 28 | end 29 | 30 | it 'raises an error if given 0 arguments' do 31 | expect do 32 | @redises.sdiff 33 | end.to raise_error(Redis::CommandError) 34 | end 35 | 36 | it 'raises an error if any argument is not a a set' do 37 | @redises.set('foo', 1) 38 | 39 | expect do 40 | @redises.sdiff(@numbers, 'foo') 41 | end.to raise_error(Redis::CommandError) 42 | 43 | expect do 44 | @redises.sdiff('foo', @numbers) 45 | end.to raise_error(Redis::CommandError) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/commands/sdiffstore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sdiffstore(destination, key [, key, ...])' do 4 | before do 5 | @numbers = 'mock-redis-test:sdiffstore:numbers' 6 | @evens = 'mock-redis-test:sdiffstore:evens' 7 | @primes = 'mock-redis-test:sdiffstore:primes' 8 | @destination = 'mock-redis-test:sdiffstore:destination' 9 | 10 | (1..10).each { |i| @redises.sadd(@numbers, i) } 11 | [2, 4, 6, 8, 10].each { |i| @redises.sadd(@evens, i) } 12 | [2, 3, 5, 7].each { |i| @redises.sadd(@primes, i) } 13 | end 14 | 15 | it 'returns the number of elements in the resulting set' do 16 | expect(@redises.sdiffstore(@destination, @numbers, @evens)).to eq(5) 17 | end 18 | 19 | it 'stores the resulting set' do 20 | @redises.sdiffstore(@destination, @numbers, @evens) 21 | expect(@redises.smembers(@destination)).to eq(%w[9 7 5 3 1]) 22 | end 23 | 24 | it 'does not store empty sets' do 25 | expect(@redises.sdiffstore(@destination, @numbers, @numbers)).to eq(0) 26 | expect(@redises.get(@destination)).to be_nil 27 | end 28 | 29 | it 'treats missing keys as empty sets' do 30 | @redises.sdiffstore(@destination, @evens, 'mock-redis-test:nonesuch') 31 | expect(@redises.smembers(@destination)).to eq(%w[10 8 6 4 2]) 32 | end 33 | 34 | it 'removes existing elements in destination' do 35 | @redises.sadd(@destination, 42) 36 | 37 | @redises.sdiffstore(@destination, @primes) 38 | expect(@redises.smembers(@destination)).to eq(%w[7 5 3 2]) 39 | end 40 | 41 | it 'raises an error if given 0 sets' do 42 | expect do 43 | @redises.sdiffstore(@destination) 44 | end.to raise_error(Redis::CommandError) 45 | end 46 | 47 | it 'raises an error if any argument is not a a set' do 48 | @redises.set('mock-redis-test:notset', 1) 49 | 50 | expect do 51 | @redises.sdiffstore(@destination, @numbers, 'mock-redis-test:notset') 52 | end.to raise_error(Redis::CommandError) 53 | 54 | expect do 55 | @redises.sdiffstore(@destination, 'mock-redis-test:notset', @numbers) 56 | end.to raise_error(Redis::CommandError) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/commands/select_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#select(db)' do 4 | before { @key = 'mock-redis-test:select' } 5 | 6 | it "returns 'OK'" do 7 | expect(@redises.select(0)).to eq('OK') 8 | end 9 | 10 | it "treats '0' and 0 the same" do 11 | @redises.select('0') 12 | @redises.set(@key, 'foo') 13 | @redises.select(0) 14 | expect(@redises.get(@key)).to eq('foo') 15 | end 16 | 17 | it 'switches databases' do 18 | @redises.select(0) 19 | @redises.set(@key, 'foo') 20 | 21 | @redises.select(1) 22 | expect(@redises.get(@key)).to be_nil 23 | 24 | @redises.select(0) 25 | expect(@redises.get(@key)).to eq('foo') 26 | end 27 | 28 | context '[mock only]' do 29 | # Time dependence introduces a bit of nondeterminism here 30 | before do 31 | @now = Time.now 32 | allow(Time).to receive(:now).and_return(@now) 33 | 34 | @mock = @redises.mock 35 | 36 | @mock.select(0) 37 | @mock.set(@key, 1) 38 | @mock.expire(@key, 100) 39 | 40 | @mock.select(1) 41 | @mock.set(@key, 2) 42 | @mock.expire(@key, 200) 43 | end 44 | 45 | it 'keeps expire times per-db' do 46 | @mock.select(0) 47 | expect(@mock.ttl(@key)).to eq(100) 48 | 49 | @mock.select(1) 50 | expect(@mock.ttl(@key)).to eq(200) 51 | end 52 | 53 | it 'keeps expire times in miliseconds per-db' do 54 | @mock.select(0) 55 | expect(100_000 - 1000..100_000 + 1000).to cover(@mock.pttl(@key)) 56 | 57 | @mock.select(1) 58 | expect(200_000 - 1000..200_000 + 1000).to cover(@mock.pttl(@key)) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/commands/setbit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#setbit(key, offset)' do 4 | before do 5 | Encoding.default_external = 'UTF-8' 6 | @key = 'mock-redis-test:setbit' 7 | @redises.set(@key, 'h') # ASCII 0x68 8 | end 9 | 10 | it "returns the original stored bit's value" do 11 | expect(@redises.setbit(@key, 0, 1)).to eq(0) 12 | expect(@redises.setbit(@key, 1, 1)).to eq(1) 13 | end 14 | 15 | it 'sets the bit within the string' do 16 | @redises.setbit(@key, 7, 1) 17 | expect(@redises.get(@key)).to eq('i') # ASCII 0x69 18 | end 19 | 20 | it 'unsets the bit within the string' do 21 | @redises.setbit(@key, 1, 0) 22 | expect(@redises.get(@key)).to eq('(') # ASCII 0x28 23 | end 24 | 25 | it 'does the right thing with multibyte characters' do 26 | @redises.set(@key, '€99.94') # the euro sign is 3 bytes wide in UTF-8 27 | expect(@redises.setbit(@key, 63, 1)).to eq(0) 28 | expect(@redises.get(@key)).to eq('€99.95') 29 | end 30 | 31 | it 'expands the string if necessary' do 32 | @redises.setbit(@key, 9, 1) 33 | expect(@redises.get(@key)).to eq('h@') 34 | end 35 | 36 | it 'sets added bits to 0' do 37 | @redises.setbit(@key, 17, 1) 38 | expect(@redises.get(@key)).to eq("h\000@") 39 | end 40 | 41 | it 'treats missing keys as empty strings' do 42 | @redises.del(@key) 43 | @redises.setbit(@key, 1, 1) 44 | expect(@redises.get(@key)).to eq('@') 45 | end 46 | 47 | it 'sets and retrieves bits' do 48 | @redises.setbit(@key, 22, 1) 49 | expect(@redises.getbit(@key, 22)).to eq(1) 50 | @redises.setbit(@key, 23, 0) 51 | expect(@redises.getbit(@key, 23)).to eq(0) 52 | end 53 | 54 | it_should_behave_like 'a string-only command', Redis::CommandError 55 | end 56 | -------------------------------------------------------------------------------- /spec/commands/setex_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#setex(key, seconds, value)' do 4 | before { @key = 'mock-redis-test:setex' } 5 | 6 | it "responds with 'OK'" do 7 | expect(@redises.setex(@key, 10, 'value')).to eq('OK') 8 | end 9 | 10 | it 'sets the value' do 11 | @redises.setex(@key, 10_000, 'value') 12 | expect(@redises.get(@key)).to eq('value') 13 | end 14 | 15 | it 'sets the expiration time' do 16 | @redises.setex(@key, 10_000, 'value') 17 | 18 | # no guarantee these are the same 19 | expect(@redises.real.ttl(@key)).to be > 0 20 | expect(@redises.mock.ttl(@key)).to be > 0 21 | end 22 | 23 | context 'when expiration time is zero' do 24 | it 'raises Redis::CommandError' do 25 | expect do 26 | @redises.setex(@key, 0, 'value') 27 | end.to raise_error(Redis::CommandError, /ERR invalid expire time in setex/) 28 | end 29 | end 30 | 31 | context 'when expiration time is negative' do 32 | it 'raises Redis::CommandError' do 33 | expect do 34 | @redises.setex(@key, -2, 'value') 35 | end.to raise_error(Redis::CommandError, /ERR invalid expire time in setex/) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/commands/setnx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#setnx(key, value)' do 4 | before { @key = 'mock-redis-test:setnx' } 5 | 6 | it 'returns true if the key was absent' do 7 | expect(@redises.setnx(@key, 1)).to eq(true) 8 | end 9 | 10 | it 'returns false if the key was present' do 11 | @redises.set(@key, 2) 12 | expect(@redises.setnx(@key, 1)).to eq(false) 13 | end 14 | 15 | it 'sets the value if missing' do 16 | @redises.setnx(@key, 'value') 17 | expect(@redises.get(@key)).to eq('value') 18 | end 19 | 20 | it 'does nothing if the value is present' do 21 | @redises.set(@key, 'old') 22 | @redises.setnx(@key, 'new') 23 | expect(@redises.get(@key)).to eq('old') 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/commands/setrange_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#setrange(key, offset, value)' do 4 | before do 5 | @key = 'mock-redis-test:setrange' 6 | @redises.set(@key, 'This is a string') 7 | end 8 | 9 | it "returns the string's new length" do 10 | expect(@redises.setrange(@key, 0, 'That')).to eq(16) 11 | end 12 | 13 | it 'updates part of the string' do 14 | @redises.setrange(@key, 0, 'That') 15 | expect(@redises.get(@key)).to eq('That is a string') 16 | end 17 | 18 | it 'zero-pads the string if necessary' do 19 | @redises.setrange(@key, 20, 'X') 20 | expect(@redises.get(@key)).to eq("This is a string\000\000\000\000X") 21 | end 22 | 23 | it 'stores things as strings' do 24 | other_key = 'mock-redis-test:setrange-other-key' 25 | @redises.setrange(other_key, 0, 1) 26 | expect(@redises.get(other_key)).to eq('1') 27 | end 28 | 29 | it_should_behave_like 'a string-only command' 30 | end 31 | -------------------------------------------------------------------------------- /spec/commands/sinter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sinter(key [, key, ...])' do 4 | before do 5 | @numbers = 'mock-redis-test:sinter:numbers' 6 | @evens = 'mock-redis-test:sinter:evens' 7 | @primes = 'mock-redis-test:sinter:primes' 8 | 9 | (1..10).each { |i| @redises.sadd(@numbers, i) } 10 | [2, 4, 6, 8, 10].each { |i| @redises.sadd(@evens, i) } 11 | [2, 3, 5, 7].each { |i| @redises.sadd(@primes, i) } 12 | end 13 | 14 | it 'returns the elements in the resulting set' do 15 | expect(@redises.sinter(@evens, @primes)).to eq(['2']) 16 | end 17 | 18 | it 'raises error when key is not set' do 19 | expect do 20 | @redises.sinter(nil, 'mock-redis-test:nonesuch') 21 | end.to raise_error(TypeError) 22 | end 23 | 24 | it 'raises an error if given 0 sets' do 25 | expect do 26 | @redises.sinter 27 | end.to raise_error(Redis::CommandError) 28 | end 29 | 30 | it 'raises an error if any argument is not a a set' do 31 | @redises.set('mock-redis-test:notset', 1) 32 | 33 | expect do 34 | @redises.sinter(@numbers, 'mock-redis-test:notset') 35 | end.to raise_error(Redis::CommandError) 36 | 37 | expect do 38 | @redises.sinter('mock-redis-test:notset', @numbers) 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/commands/sinterstore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sinterstore(destination, key [, key, ...])' do 4 | before do 5 | @numbers = 'mock-redis-test:sinterstore:numbers' 6 | @evens = 'mock-redis-test:sinterstore:evens' 7 | @primes = 'mock-redis-test:sinterstore:primes' 8 | @destination = 'mock-redis-test:sinterstore:destination' 9 | 10 | (1..10).each { |i| @redises.sadd(@numbers, i) } 11 | [2, 4, 6, 8, 10].each { |i| @redises.sadd(@evens, i) } 12 | [2, 3, 5, 7].each { |i| @redises.sadd(@primes, i) } 13 | end 14 | 15 | it 'returns the number of elements in the resulting set' do 16 | expect(@redises.sinterstore(@destination, @numbers, @evens)).to eq(5) 17 | end 18 | 19 | it 'stores the resulting set' do 20 | @redises.sinterstore(@destination, @numbers, @evens) 21 | expect(@redises.smembers(@destination)).to eq(%w[10 8 6 4 2]) 22 | end 23 | 24 | it 'does not store empty sets' do 25 | expect(@redises.sinterstore(@destination, 'mock-redis-test:nonesuch', @numbers)).to eq(0) 26 | expect(@redises.get(@destination)).to be_nil 27 | end 28 | 29 | it 'removes existing elements in destination' do 30 | @redises.sadd(@destination, 42) 31 | 32 | @redises.sinterstore(@destination, @primes) 33 | expect(@redises.smembers(@destination)).to eq(%w[7 5 3 2]) 34 | end 35 | 36 | it 'raises an error if given 0 sets' do 37 | expect do 38 | @redises.sinterstore(@destination) 39 | end.to raise_error(Redis::CommandError) 40 | end 41 | 42 | it 'raises an error if any argument is not a a set' do 43 | @redises.set('mock-redis-test:notset', 1) 44 | 45 | expect do 46 | @redises.sinterstore(@destination, @numbers, 'mock-redis-test:notset') 47 | end.to raise_error(Redis::CommandError) 48 | 49 | expect do 50 | @redises.sinterstore(@destination, 'mock-redis-test:notset', @numbers) 51 | end.to raise_error(Redis::CommandError) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/commands/sismember_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sismember(key, member)' do 4 | before do 5 | @key = 'mock-redis-test:sismember' 6 | @redises.sadd(@key, 'whiskey') 7 | @redises.sadd(@key, 'beer') 8 | end 9 | 10 | it 'returns true if member is in set' do 11 | expect(@redises.sismember(@key, 'whiskey')).to eq(true) 12 | expect(@redises.sismember(@key, 'beer')).to eq(true) 13 | end 14 | 15 | it 'returns false if member is not in set' do 16 | expect(@redises.sismember(@key, 'cola')).to eq(false) 17 | end 18 | 19 | it 'stringifies member' do 20 | @redises.sadd(@key, '1') 21 | expect(@redises.sismember(@key, 1)).to eq(true) 22 | end 23 | 24 | it 'treats a nonexistent value as an empty set' do 25 | expect(@redises.sismember('mock-redis-test:nonesuch', 'beer')).to eq(false) 26 | end 27 | 28 | it_should_behave_like 'a set-only command' 29 | end 30 | -------------------------------------------------------------------------------- /spec/commands/smembers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#smembers(key)' do 4 | before { @key = 'mock-redis-test:smembers' } 5 | 6 | it 'returns [] for an empty set' do 7 | expect(@redises.smembers(@key)).to eq([]) 8 | end 9 | 10 | it "returns the set's members" do 11 | @redises.sadd(@key, 'Hello') 12 | @redises.sadd(@key, 'World') 13 | @redises.sadd(@key, 'Test') 14 | expect(@redises.smembers(@key)).to eq(%w[Test World Hello]) 15 | end 16 | 17 | it 'returns unfrozen copies of the input' do 18 | input = 'a string' 19 | @redises.sadd(@key, input) 20 | output = @redises.smembers(@key).first 21 | 22 | expect(output).to eq input 23 | expect(output).to_not equal input 24 | expect(output).to_not be_frozen 25 | end 26 | 27 | it_should_behave_like 'a set-only command' 28 | end 29 | -------------------------------------------------------------------------------- /spec/commands/smismember_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#smismember(key, *members)' do 4 | before do 5 | @key = 'mock-redis-test:smismember' 6 | @redises.sadd(@key, 'whiskey') 7 | @redises.sadd(@key, 'beer') 8 | end 9 | 10 | it 'returns true if member is in set' do 11 | expect(@redises.smismember(@key, 'whiskey')).to eq([true]) 12 | expect(@redises.smismember(@key, 'beer')).to eq([true]) 13 | expect(@redises.smismember(@key, 'whiskey', 'beer')).to eq([true, true]) 14 | expect(@redises.smismember(@key, %w[whiskey beer])).to eq([true, true]) 15 | end 16 | 17 | it 'returns false if member is not in set' do 18 | expect(@redises.smismember(@key, 'cola')).to eq([false]) 19 | expect(@redises.smismember(@key, 'whiskey', 'cola')).to eq([true, false]) 20 | expect(@redises.smismember(@key, %w[whiskey beer cola])).to eq([true, true, false]) 21 | end 22 | 23 | it 'stringifies member' do 24 | @redises.sadd(@key, '1') 25 | expect(@redises.smismember(@key, 1)).to eq([true]) 26 | expect(@redises.smismember(@key, [1])).to eq([true]) 27 | end 28 | 29 | it 'treats a nonexistent value as an empty set' do 30 | expect(@redises.smismember('mock-redis-test:nonesuch', 'beer')).to eq([false]) 31 | end 32 | 33 | it_should_behave_like 'a set-only command' 34 | end 35 | -------------------------------------------------------------------------------- /spec/commands/smove_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#smove(source, destination, member)' do 4 | before do 5 | @src = 'mock-redis-test:smove-source' 6 | @dest = 'mock-redis-test:smove-destination' 7 | 8 | @redises.sadd(@src, 1) 9 | @redises.sadd(@dest, 2) 10 | end 11 | 12 | it 'returns true if the member exists in src' do 13 | expect(@redises.smove(@src, @dest, 1)).to eq(true) 14 | end 15 | 16 | it 'returns false if the member exists in src' do 17 | expect(@redises.smove(@src, @dest, 'nope')).to eq(false) 18 | end 19 | 20 | it 'returns true if the member exists in src and dest' do 21 | @redises.sadd(@dest, 1) 22 | expect(@redises.smove(@src, @dest, 1)).to eq(true) 23 | end 24 | 25 | it 'moves member from source to destination' do 26 | @redises.smove(@src, @dest, 1) 27 | expect(@redises.sismember(@dest, 1)).to eq(true) 28 | expect(@redises.sismember(@src, 1)).to eq(false) 29 | end 30 | 31 | it 'cleans up empty sets' do 32 | @redises.smove(@src, @dest, 1) 33 | expect(@redises.get(@src)).to be_nil 34 | end 35 | 36 | it 'treats a nonexistent value as an empty set' do 37 | expect(@redises.smove('mock-redis-test:nonesuch', @dest, 1)).to eq(false) 38 | end 39 | 40 | it_should_behave_like 'a set-only command' 41 | end 42 | -------------------------------------------------------------------------------- /spec/commands/sort_list_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sort(key, options)' do 4 | before do 5 | @key = 'mock-redis-test:list_sort' 6 | 7 | @redises.rpush(@key, '1') 8 | @redises.rpush(@key, '2') 9 | 10 | @redises.set('mock-redis-test:values_1', 'a') 11 | @redises.set('mock-redis-test:values_2', 'b') 12 | 13 | @redises.set('mock-redis-test:weight_1', '2') 14 | @redises.set('mock-redis-test:weight_2', '1') 15 | 16 | @redises.hset('mock-redis-test:hash_1', 'key', 'x') 17 | @redises.hset('mock-redis-test:hash_2', 'key', 'y') 18 | end 19 | 20 | it_should_behave_like 'a sortable' 21 | end 22 | -------------------------------------------------------------------------------- /spec/commands/sort_set_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sort(key, options)' do 4 | before do 5 | @key = 'mock-redis-test:set_sort' 6 | 7 | @redises.sadd(@key, '1') 8 | @redises.sadd(@key, '2') 9 | 10 | @redises.set('mock-redis-test:values_1', 'a') 11 | @redises.set('mock-redis-test:values_2', 'b') 12 | 13 | @redises.set('mock-redis-test:weight_1', '2') 14 | @redises.set('mock-redis-test:weight_2', '1') 15 | 16 | @redises.hset('mock-redis-test:hash_1', 'key', 'x') 17 | @redises.hset('mock-redis-test:hash_2', 'key', 'y') 18 | end 19 | 20 | it_should_behave_like 'a sortable' 21 | end 22 | -------------------------------------------------------------------------------- /spec/commands/sort_zset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sort(key, options)' do 4 | before do 5 | @key = 'mock-redis-test:zset_sort' 6 | 7 | @redises.zadd(@key, 100, '1') 8 | @redises.zadd(@key, 99, '2') 9 | 10 | @redises.set('mock-redis-test:values_1', 'a') 11 | @redises.set('mock-redis-test:values_2', 'b') 12 | 13 | @redises.set('mock-redis-test:weight_1', '2') 14 | @redises.set('mock-redis-test:weight_2', '1') 15 | 16 | @redises.hset('mock-redis-test:hash_1', 'key', 'x') 17 | @redises.hset('mock-redis-test:hash_2', 'key', 'y') 18 | end 19 | 20 | it_should_behave_like 'a sortable' 21 | end 22 | -------------------------------------------------------------------------------- /spec/commands/spop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#spop(key)' do 4 | before do 5 | @key = 'mock-redis-test:spop' 6 | 7 | @redises.sadd(@key, 'value') 8 | end 9 | 10 | it 'returns a member of the set' do 11 | expect(@redises.spop(@key)).to eq('value') 12 | end 13 | 14 | it 'removes a member of the set' do 15 | @redises.spop(@key) 16 | expect(@redises.smembers(@key)).to eq([]) 17 | end 18 | 19 | it 'returns nil if the set is empty' do 20 | @redises.spop(@key) 21 | expect(@redises.spop(@key)).to be_nil 22 | end 23 | 24 | it 'returns an array if count is not nil' do 25 | @redises.sadd(@key, 'value2') 26 | expect(@redises.spop(@key, 2)).to eq(%w[value value2]) 27 | end 28 | 29 | it 'returns only whats in the set' do 30 | expect(@redises.spop(@key, 2)).to eq(['value']) 31 | expect(@redises.smembers(@key)).to eq([]) 32 | end 33 | 34 | it 'returns an empty array if count is not nil and the set it empty' do 35 | @redises.spop(@key) 36 | expect(@redises.spop(@key, 100)).to eq([]) 37 | end 38 | 39 | it_should_behave_like 'a set-only command' 40 | end 41 | -------------------------------------------------------------------------------- /spec/commands/srandmember_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#srandmember(key)' do 4 | before do 5 | @key = 'mock-redis-test:srandmember' 6 | 7 | @redises.sadd(@key, 'value') 8 | end 9 | 10 | it 'returns a member of the set' do 11 | expect(@redises.srandmember(@key)).to eq('value') 12 | end 13 | 14 | it 'does not modify the set' do 15 | @redises.srandmember(@key) 16 | expect(@redises.smembers(@key)).to eq(['value']) 17 | end 18 | 19 | it 'returns nil if the set is empty' do 20 | @redises.spop(@key) 21 | expect(@redises.srandmember(@key)).to be_nil 22 | end 23 | 24 | context 'when count argument is specified' do 25 | before do 26 | @redises.sadd(@key, 'value2') 27 | @redises.sadd(@key, 'value3') 28 | end 29 | 30 | # NOTE: We disable result checking since MockRedis and Redis will likely 31 | # return a different random set (since the selection is, well, random) 32 | it 'returns the whole set if count is greater than the set size' do 33 | expect(@redises.send_without_checking(:srandmember, @key, 5)) 34 | .to match_array(%w[value value2 value3]) 35 | end 36 | 37 | it 'returns random members up to count from the set when count is smaller than the set size' do 38 | expect(@redises.send_without_checking(:srandmember, @key, 2).size).to eq(2) 39 | end 40 | 41 | it 'returns random members up to count from the set when count is negative even if count.abs is greater than the set size' do # rubocop:disable Layout/LineLength 42 | expect(@redises.send_without_checking(:srandmember, @key, -5).size).to eq(5) 43 | end 44 | 45 | it 'returns nil if the set is empty' do 46 | @redises.srem(@key, %w[value value2 value3]) 47 | expect(@redises.srandmember(@key, 2)).to be_empty 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/commands/srem_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#srem(key, member)' do 4 | before do 5 | @key = 'mock-redis-test:srem' 6 | 7 | @redises.sadd(@key, 'bert') 8 | @redises.sadd(@key, 'ernie') 9 | end 10 | 11 | it 'returns true if member is in the set' do 12 | expect(@redises.srem(@key, 'bert')).to eq(1) 13 | end 14 | 15 | it 'returns false if member is not in the set' do 16 | expect(@redises.srem(@key, 'cookiemonster')).to eq(0) 17 | end 18 | 19 | it 'removes member from the set' do 20 | @redises.srem(@key, 'ernie') 21 | expect(@redises.smembers(@key)).to eq(['bert']) 22 | end 23 | 24 | it 'stringifies member' do 25 | @redises.sadd(@key, '1') 26 | expect(@redises.srem(@key, 1)).to eq(1) 27 | end 28 | 29 | it 'cleans up empty sets' do 30 | @redises.smembers(@key).each { |m| @redises.srem(@key, m) } 31 | expect(@redises.get(@key)).to be_nil 32 | end 33 | 34 | it 'supports a variable number of arguments' do 35 | expect(@redises.srem(@key, %w[bert ernie])).to eq(2) 36 | expect(@redises.get(@key)).to be_nil 37 | end 38 | 39 | it 'allow passing an array of integers as argument' do 40 | @redises.sadd(@key, %w[1 2]) 41 | expect(@redises.srem(@key, [1, 2])).to eq(2) 42 | end 43 | 44 | context 'srem?' do 45 | it 'returns true if member is in the set' do 46 | expect(@redises.srem?(@key, 'bert')).to eq(true) 47 | end 48 | 49 | it 'returns false if member is not in the set' do 50 | expect(@redises.srem?(@key, 'cookiemonster')).to eq(false) 51 | end 52 | 53 | it 'removes member from the set' do 54 | @redises.srem?(@key, 'ernie') 55 | expect(@redises.smembers(@key)).to eq(['bert']) 56 | end 57 | end 58 | 59 | it_should_behave_like 'a set-only command' 60 | end 61 | -------------------------------------------------------------------------------- /spec/commands/sscan_each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sscan_each' do 4 | subject { MockRedis::Database.new(self) } 5 | 6 | let(:key) { 'mock-redis-test:sscan_each' } 7 | 8 | before do 9 | allow(subject).to receive(:smembers).and_return(collection) 10 | end 11 | 12 | context 'when no keys are found' do 13 | let(:collection) { [] } 14 | 15 | it 'does not iterate over any elements' do 16 | results = subject.sscan_each(key).map do |k| 17 | k 18 | end 19 | expect(results).to match_array(collection) 20 | end 21 | end 22 | 23 | context 'when keys are found' do 24 | context 'when no match filter is supplied' do 25 | let(:collection) { Array.new(20) { |i| "k#{i}" } } 26 | 27 | it 'iterates over each item in the collection' do 28 | results = subject.sscan_each(key).map do |k| 29 | k 30 | end 31 | expect(results).to match_array(collection) 32 | end 33 | end 34 | 35 | context 'when giving a custom match filter' do 36 | let(:match) { 'k1*' } 37 | let(:collection) { Array.new(12) { |i| "k#{i}" } } 38 | let(:expected) { %w[k1 k10 k11] } 39 | 40 | it 'iterates over each item in the filtered collection' do 41 | results = subject.sscan_each(key, match: match).map do |k| 42 | k 43 | end 44 | expect(results).to match_array(expected) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/commands/sscan_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sscan' do 4 | let(:count) { 10 } 5 | let(:match) { '*' } 6 | let(:key) { 'mock-redis-test:sscan' } 7 | 8 | before do 9 | # The return order of the members is non-deterministic, so we sort them to compare 10 | expect_any_instance_of(Redis).to receive(:sscan).and_wrap_original do |m, *args, **kwargs| 11 | result = m.call(*args, **kwargs) 12 | [result[0], result[1].sort] 13 | end 14 | expect_any_instance_of(MockRedis).to receive(:sscan).and_wrap_original do |m, *args, **kwargs| 15 | result = m.call(*args, **kwargs) 16 | [result[0], result[1].sort] 17 | end 18 | end 19 | 20 | context 'when the set does not exist' do 21 | it 'returns a 0 cursor and an empty collection' do 22 | expect(@redises.sscan(key, 0, count: count, match: match)).to eq(['0', []]) 23 | end 24 | end 25 | 26 | context 'when the set exists' do 27 | before do 28 | @redises.sadd(key, 'Hello') 29 | @redises.sadd(key, 'World') 30 | @redises.sadd(key, 'Test') 31 | end 32 | 33 | let(:expected) { ['0', %w[Hello Test World]] } 34 | 35 | it 'returns a 0 cursor and the collection' do 36 | expect(@redises.sscan(key, 0, count: count)).to eq(expected) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/commands/strlen_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#strlen(key)' do 4 | before do 5 | @key = 'mock-redis-test:73288' 6 | @redises.set(@key, '5 ∈ (0..10)') 7 | end 8 | 9 | it "returns the string's length in bytes" do 10 | expect(@redises.strlen(@key)).to eq(13) 11 | end 12 | 13 | it 'returns 0 for a nonexistent value' do 14 | expect(@redises.strlen('mock-redis-test:does-not-exist')).to eq(0) 15 | end 16 | 17 | it_should_behave_like 'a string-only command' 18 | end 19 | -------------------------------------------------------------------------------- /spec/commands/sunion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sunion(key [, key, ...])' do 4 | before do 5 | @evens = 'mock-redis-test:sunion:evens' 6 | @primes = 'mock-redis-test:sunion:primes' 7 | 8 | [2, 4, 6, 8, 10].each { |i| @redises.sadd(@evens, i) } 9 | [2, 3, 5, 7].each { |i| @redises.sadd(@primes, i) } 10 | end 11 | 12 | it 'returns the elements in the resulting set' do 13 | expect(@redises.sunion(@evens, @primes)).to eq(%w[2 4 6 8 10 3 5 7]) 14 | end 15 | 16 | it 'treats missing keys as empty sets' do 17 | expect(@redises.sunion(@primes, 'mock-redis-test:nonesuch')). 18 | to eq(%w[2 3 5 7]) 19 | end 20 | 21 | it 'allows Array as argument' do 22 | expect(@redises.sunion([@evens, @primes])).to eq(%w[2 4 6 8 10 3 5 7]) 23 | end 24 | 25 | it 'raises an error if given 0 sets' do 26 | expect do 27 | @redises.sunion 28 | end.to raise_error(Redis::CommandError) 29 | end 30 | 31 | it 'raises an error if any argument is not a a set' do 32 | @redises.set('mock-redis-test:notset', 1) 33 | 34 | expect do 35 | @redises.sunion(nil, 'mock-redis-test:notset') 36 | end.to raise_error(TypeError) 37 | 38 | expect do 39 | @redises.sunion('mock-redis-test:notset', nil) 40 | end.to raise_error(TypeError) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/commands/sunionstore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#sunionstore(destination, key [, key, ...])' do 4 | before do 5 | @evens = 'mock-redis-test:sunionstore:evens' 6 | @primes = 'mock-redis-test:sunionstore:primes' 7 | @destination = 'mock-redis-test:sunionstore:destination' 8 | 9 | [2, 4, 6, 8, 10].each { |i| @redises.sadd(@evens, i) } 10 | [2, 3, 5, 7].each { |i| @redises.sadd(@primes, i) } 11 | end 12 | 13 | it 'returns the number of elements in the resulting set' do 14 | expect(@redises.sunionstore(@destination, @primes, @evens)).to eq(8) 15 | end 16 | 17 | it 'stores the resulting set' do 18 | @redises.sunionstore(@destination, @primes, @evens) 19 | expect(@redises.smembers(@destination)).to eq(%w[10 8 6 4 7 5 3 2]) 20 | end 21 | 22 | it 'does not store empty sets' do 23 | expect(@redises.sunionstore(@destination, 24 | 'mock-redis-test:nonesuch', 25 | 'mock-redis-test:nonesuch2')).to eq(0) 26 | expect(@redises.get(@destination)).to be_nil 27 | end 28 | 29 | it 'removes existing elements in destination' do 30 | @redises.sadd(@destination, 42) 31 | 32 | @redises.sunionstore(@destination, @primes) 33 | expect(@redises.smembers(@destination)).to eq(%w[7 5 3 2]) 34 | end 35 | 36 | it 'correctly unions and stores when the destination is empty and is one of the arguments' do 37 | @redises.sunionstore(@destination, @destination, @primes) 38 | 39 | expect(@redises.smembers(@destination)).to eq(%w[7 5 3 2]) 40 | end 41 | 42 | it 'raises an error if given 0 sets' do 43 | expect do 44 | @redises.sunionstore(@destination) 45 | end.to raise_error(Redis::CommandError) 46 | end 47 | 48 | it 'raises an error if any argument is not a a set' do 49 | @redises.set('mock-redis-test:notset', 1) 50 | 51 | expect do 52 | @redises.sunionstore(@destination, @primes, 'mock-redis-test:notset') 53 | end.to raise_error(Redis::CommandError) 54 | 55 | expect do 56 | @redises.sunionstore(@destination, 'mock-redis-test:notset', @primes) 57 | end.to raise_error(Redis::CommandError) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/commands/ttl_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#ttl(key)' do 4 | before do 5 | @key = 'mock-redis-test:persist' 6 | @redises.set(@key, 'spork') 7 | end 8 | 9 | it 'returns -1 for a key with no expiration' do 10 | expect(@redises.ttl(@key)).to eq(-1) 11 | end 12 | 13 | it 'returns -2 for a key that does not exist' do 14 | expect(@redises.ttl('mock-redis-test:nonesuch')).to eq(-2) 15 | end 16 | 17 | it 'stringifies key' do 18 | @redises.expire(@key, 9) 19 | expect(@redises.ttl(@key.to_sym)).to be > 0 20 | end 21 | 22 | context '[mock only]' do 23 | # These are mock-only since we can't actually manipulate time in 24 | # the real Redis. 25 | 26 | before(:all) do 27 | @mock = @redises.mock 28 | end 29 | 30 | before do 31 | @now = Time.now 32 | allow(Time).to receive(:now).and_return(@now) 33 | end 34 | 35 | it "gives you the key's remaining lifespan in seconds" do 36 | @mock.expire(@key, 5) 37 | expect(@mock.ttl(@key)).to eq(5) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/commands/type_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#type(key)' do 4 | before do 5 | @key = 'mock-redis-test:type' 6 | end 7 | 8 | it "returns 'none' for no key" do 9 | expect(@redises.type(@key)).to eq('none') 10 | end 11 | 12 | it "returns 'string' for a string" do 13 | @redises.set(@key, 'stringlish') 14 | expect(@redises.type(@key)).to eq('string') 15 | end 16 | 17 | it "returns 'list' for a list" do 18 | @redises.lpush(@key, 100) 19 | expect(@redises.type(@key)).to eq('list') 20 | end 21 | 22 | it "returns 'hash' for a hash" do 23 | @redises.hset(@key, 100, 200) 24 | expect(@redises.type(@key)).to eq('hash') 25 | end 26 | 27 | it "returns 'set' for a set" do 28 | @redises.sadd(@key, 100) 29 | expect(@redises.type(@key)).to eq('set') 30 | end 31 | 32 | it "returns 'zset' for a zset" do 33 | @redises.zadd(@key, 1, 2) 34 | expect(@redises.type(@key)).to eq('zset') 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/commands/unwatch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#unwatch' do 4 | it "responds with 'OK'" do 5 | expect(@redises.unwatch).to eq('OK') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/commands/watch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#watch(key [, key, ...)' do 4 | before do 5 | @key1 = 'mock-redis-test-watch1' 6 | @key2 = 'mock-redis-test-watch2' 7 | end 8 | 9 | it "returns 'OK'" do 10 | expect(@redises.watch(@key1, @key2)).to eq('OK') 11 | end 12 | 13 | it 'EXECs its MULTI on successes' do 14 | @redises.watch @key1, @key2 do 15 | @redises.multi do |multi| 16 | multi.set 'bar', 'baz' 17 | end 18 | end 19 | expect(@redises.get('bar')).to eq('baz') 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/commands/xlen_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#xlen(key)' do 4 | before :all do 5 | sleep 1 - (Time.now.to_f % 1) 6 | @key = 'mock-redis-test:xlen' 7 | end 8 | 9 | before :each do 10 | # TODO: Redis appears to be returning a timestamp a few seconds in the future 11 | # so we're ignoring the last 5 digits (time in milliseconds) 12 | @redises._gsub(/\d{5}-\d/, '...-.') 13 | end 14 | 15 | it 'returns the number of items in the stream' do 16 | expect(@redises.xlen(@key)).to eq 0 17 | @redises.xadd(@key, { key: 'value' }) 18 | expect(@redises.xlen(@key)).to eq 1 19 | 3.times { @redises.xadd(@key, { key: 'value' }) } 20 | expect(@redises.xlen(@key)).to eq 4 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/commands/xtrim_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#xtrim("mystream", 1000, approximate: true)' do 4 | before { @key = 'mock-redis-test:xtrim' } 5 | 6 | before :each do 7 | @redises.xadd(@key, { key1: 'value1' }, id: '1234567891234-0') 8 | @redises.xadd(@key, { key2: 'value2' }, id: '1234567891245-0') 9 | @redises.xadd(@key, { key3: 'value3' }, id: '1234567891245-1') 10 | @redises.xadd(@key, { key4: 'value4' }, id: '1234567891278-0') 11 | @redises.xadd(@key, { key5: 'value5' }, id: '1234567891278-1') 12 | @redises.xadd(@key, { key6: 'value6' }, id: '1234567891299-0') 13 | end 14 | 15 | it 'returns the number of elements deleted' do 16 | expect(@redises.xtrim(@key, 4)).to eq 2 17 | end 18 | 19 | it 'returns 0 if count is greater than size' do 20 | initial = @redises.xrange(@key, '-', '+') 21 | expect(@redises.xtrim(@key, 1000)).to eq 0 22 | expect(@redises.xrange(@key, '-', '+')).to eql(initial) 23 | end 24 | 25 | it 'deletes the oldes elements' do 26 | @redises.xtrim(@key, 4) 27 | expect(@redises.xrange(@key, '-', '+')).to eq( 28 | [ 29 | ['1234567891245-1', { 'key3' => 'value3' }], 30 | ['1234567891278-0', { 'key4' => 'value4' }], 31 | ['1234567891278-1', { 'key5' => 'value5' }], 32 | ['1234567891299-0', { 'key6' => 'value6' }] 33 | ] 34 | ) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/commands/zcard_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zcard(key)' do 4 | before do 5 | @key = 'mock-redis-test:zcard' 6 | end 7 | 8 | it 'returns the number of elements in the zset' do 9 | @redises.zadd(@key, 1, 'Washington') 10 | @redises.zadd(@key, 2, 'Adams') 11 | expect(@redises.zcard(@key)).to eq(2) 12 | end 13 | 14 | it 'returns 0 for nonexistent sets' do 15 | expect(@redises.zcard(@key)).to eq(0) 16 | end 17 | 18 | it_should_behave_like 'a zset-only command' 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/zcount_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zcount(key, min, max)' do 4 | before do 5 | @key = 'mock-redis-test:zcount' 6 | @redises.zadd(@key, 1, 'Washington') 7 | @redises.zadd(@key, 2, 'Adams') 8 | @redises.zadd(@key, 3, 'Jefferson') 9 | @redises.zadd(@key, 4, 'Madison') 10 | end 11 | 12 | it 'returns the number of members in the zset with scores in (min..max)' do 13 | expect(@redises.zcount(@key, 3, 10)).to eq(2) 14 | end 15 | 16 | it 'returns 0 if there are no such members' do 17 | expect(@redises.zcount(@key, 100, 200)).to eq(0) 18 | end 19 | 20 | it 'returns count of all elements when -inf to +inf' do 21 | expect(@redises.zcount(@key, '-inf', '+inf')).to eq(4) 22 | end 23 | 24 | it 'returns a proper count of elements using +inf upper bound' do 25 | expect(@redises.zcount(@key, 3, '+inf')).to eq(2) 26 | end 27 | 28 | it 'returns a proper count of elements using exclusive lower bound' do 29 | expect(@redises.zcount(@key, '(3', '+inf')).to eq(1) 30 | end 31 | 32 | it 'returns a proper count of elements using exclusive upper bound' do 33 | expect(@redises.zcount(@key, '-inf', '(3')).to eq(2) 34 | end 35 | 36 | it_should_behave_like 'arg 1 is a score' 37 | it_should_behave_like 'arg 2 is a score' 38 | it_should_behave_like 'a zset-only command' 39 | end 40 | -------------------------------------------------------------------------------- /spec/commands/zincrby_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zincrby(key, increment, member)' do 4 | before do 5 | @key = 'mock-redis-test:zincrby' 6 | @redises.zadd(@key, 1, 'bert') 7 | end 8 | 9 | it 'returns the new score as a string' do 10 | expect(@redises.zincrby(@key, 10, 'bert')).to eq(11.0) 11 | end 12 | 13 | it "updates the item's score" do 14 | @redises.zincrby(@key, 10, 'bert') 15 | expect(@redises.zscore(@key, 'bert')).to eq(11.0) 16 | end 17 | 18 | it 'handles integer members correctly' do 19 | member = 11 20 | @redises.zadd(@key, 1, member) 21 | @redises.zincrby(@key, 1, member) 22 | expect(@redises.zscore(@key, member)).to eq(2.0) 23 | end 24 | 25 | it 'adds missing members with score increment' do 26 | expect(@redises.zincrby(@key, 5.5, 'bigbird')).to eq(5.5) 27 | end 28 | 29 | it_should_behave_like 'arg 1 is a score' 30 | it_should_behave_like 'a zset-only command' 31 | end 32 | -------------------------------------------------------------------------------- /spec/commands/zmscore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zmscore(key, member)' do 4 | before { @key = 'mock-redis-test:zmscore' } 5 | 6 | it 'returns the score as a string' do 7 | expect(@redises.zadd(@key, 0.25, 'foo')).to eq(true) 8 | expect(@redises.zmscore(@key, 'foo')).to eq([0.25]) 9 | end 10 | 11 | it 'handles integer members correctly' do 12 | member = 11 13 | expect(@redises.zadd(@key, 0.25, member)).to eq(true) 14 | expect(@redises.zmscore(@key, member)).to eq([0.25]) 15 | end 16 | 17 | it 'returns nil if member is not present in the set' do 18 | expect(@redises.zmscore(@key, 'foo')).to eq([nil]) 19 | end 20 | 21 | it 'supports a variable number of arguments' do 22 | @redises.zadd(@key, [[1, 'one'], [2, 'two']]) 23 | expect(@redises.zmscore(@key, 'one', 'three', 'two')).to eq([1, nil, 2]) 24 | end 25 | 26 | it_should_behave_like 'a zset-only command' 27 | end 28 | -------------------------------------------------------------------------------- /spec/commands/zpopmax_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zpopmax(key, count)' do 4 | before(:each) do 5 | @key = 'mock-redis-test:zpopmax' 6 | @redises.del(@key) 7 | @redises.zadd(@key, 1, 'one') 8 | @redises.zadd(@key, 2, 'two') 9 | @redises.zadd(@key, 3, 'three') 10 | end 11 | 12 | context 'when count is unspecified' do 13 | it 'returns nil if the set does not exist' do 14 | expect(@redises.zpopmax('does-not-exist')).to be_nil 15 | end 16 | 17 | it 'returns the highest ranked element' do 18 | expect(@redises.zpopmax(@key)).to eq(['three', 3]) 19 | expect(@redises.zcard(@key)).to eq(2) 20 | end 21 | end 22 | 23 | context 'when count is 1' do 24 | let(:count) { 1 } 25 | 26 | it 'returns nil if the set does not exist' do 27 | expect(@redises.zpopmax('does-not-exist', count)).to be_nil 28 | end 29 | 30 | it 'returns the highest ranked element' do 31 | expect(@redises.zpopmax(@key, count)).to eq(['three', 3]) 32 | expect(@redises.zcard(@key)).to eq(2) 33 | end 34 | end 35 | 36 | context 'when count is greater than 1' do 37 | let(:count) { 2 } 38 | 39 | it 'returns empty array if the set does not exist' do 40 | expect(@redises.zpopmax('does-not-exist', count)).to eq([]) 41 | end 42 | 43 | it 'returns the highest ranked elements' do 44 | expect(@redises.zpopmax(@key, count)).to eq([['three', 3], ['two', 2]]) 45 | expect(@redises.zcard(@key)).to eq(1) 46 | end 47 | end 48 | 49 | context 'when count is greater than the size of the set' do 50 | let(:count) { 4 } 51 | 52 | it 'returns the entire set' do 53 | before = @redises.zrange(@key, 0, count, with_scores: true).reverse 54 | expect(@redises.zpopmax(@key, count)).to eq(before) 55 | expect(@redises.zcard(@key)).to eq(0) 56 | end 57 | end 58 | 59 | it_should_behave_like 'a zset-only command' 60 | end 61 | -------------------------------------------------------------------------------- /spec/commands/zpopmin_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zpopmin(key, count)' do 4 | before(:each) do 5 | @key = 'mock-redis-test:zpopmin' 6 | @redises.del(@key) 7 | @redises.zadd(@key, 1, 'one') 8 | @redises.zadd(@key, 2, 'two') 9 | @redises.zadd(@key, 3, 'three') 10 | end 11 | 12 | context 'when count is unspecified' do 13 | it 'returns nil if the set does not exist' do 14 | expect(@redises.zpopmin('does-not-exist')).to be_nil 15 | end 16 | 17 | it 'returns the lowest ranked element' do 18 | expect(@redises.zpopmin(@key)).to eq(['one', 1]) 19 | expect(@redises.zcard(@key)).to eq(2) 20 | end 21 | end 22 | 23 | context 'when count is 1' do 24 | let(:count) { 1 } 25 | 26 | it 'returns nil if the set does not exist' do 27 | expect(@redises.zpopmin('does-not-exist', count)).to be_nil 28 | end 29 | 30 | it 'returns the lowest ranked element' do 31 | expect(@redises.zpopmin(@key, count)).to eq(['one', 1]) 32 | expect(@redises.zcard(@key)).to eq(2) 33 | end 34 | end 35 | 36 | context 'when count is greater than 1' do 37 | let(:count) { 2 } 38 | 39 | it 'returns empty array if the set does not exist' do 40 | expect(@redises.zpopmin('does-not-exist', count)).to eq([]) 41 | end 42 | 43 | it 'returns the lowest ranked elements' do 44 | expect(@redises.zpopmin(@key, count)).to eq([['one', 1], ['two', 2]]) 45 | expect(@redises.zcard(@key)).to eq(1) 46 | end 47 | end 48 | 49 | context 'when count is greater than the size of the set' do 50 | let(:count) { 4 } 51 | 52 | it 'returns the entire set' do 53 | before = @redises.zrange(@key, 0, count, with_scores: true) 54 | expect(@redises.zpopmin(@key, count)).to eq(before) 55 | expect(@redises.zcard(@key)).to eq(0) 56 | end 57 | end 58 | 59 | it_should_behave_like 'a zset-only command' 60 | end 61 | -------------------------------------------------------------------------------- /spec/commands/zrank_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zrank(key, member)' do 4 | before do 5 | @key = 'mock-redis-test:zrank' 6 | 7 | @redises.zadd(@key, 1, 'one') 8 | @redises.zadd(@key, 2, 'two') 9 | @redises.zadd(@key, 3, 'three') 10 | end 11 | 12 | it "returns nil if member wasn't present in the set" do 13 | expect(@redises.zrank(@key, 'foo')).to be_nil 14 | end 15 | 16 | it 'returns the index of the member in the set' do 17 | expect(@redises.zrank(@key, 'one')).to eq(0) 18 | expect(@redises.zrank(@key, 'two')).to eq(1) 19 | expect(@redises.zrank(@key, 'three')).to eq(2) 20 | end 21 | 22 | it 'handles integer members correctly' do 23 | member = 11 24 | @redises.zadd(@key, 4, member) 25 | expect(@redises.zrank(@key, member)).to eq(3) 26 | end 27 | 28 | it_should_behave_like 'a zset-only command' 29 | end 30 | -------------------------------------------------------------------------------- /spec/commands/zrem_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zrem(key, member)' do 4 | before do 5 | @key = 'mock-redis-test:zrem' 6 | 7 | @redises.zadd(@key, 1, 'one') 8 | @redises.zadd(@key, 2, 'two') 9 | end 10 | 11 | it 'returns true if member is present in the set' do 12 | expect(@redises.zrem(@key, 'one')).to eq(true) 13 | end 14 | 15 | it 'returns false if member is not present in the set' do 16 | expect(@redises.zrem(@key, 'nobody home')).to eq(false) 17 | end 18 | 19 | it 'removes member from the set' do 20 | @redises.zrem(@key, 'one') 21 | expect(@redises.zrange(@key, 0, -1)).to eq(['two']) 22 | end 23 | 24 | it 'removes integer member from the set' do 25 | member = 11 26 | @redises.zadd(@key, 3, member) 27 | expect(@redises.zrem(@key, member)).to eq(true) 28 | expect(@redises.zrange(@key, 0, -1)).to eq(%w[one two]) 29 | end 30 | 31 | it 'removes integer members inside an array from the set' do 32 | member = 11 33 | @redises.zadd(@key, 3, member) 34 | expect(@redises.zrem(@key, [member])).to eq(1) 35 | expect(@redises.zrange(@key, 0, -1)).to eq(%w[one two]) 36 | end 37 | 38 | it 'supports a variable number of arguments' do 39 | @redises.zrem(@key, %w[one two]) 40 | expect(@redises.zrange(@key, 0, -1)).to be_empty 41 | end 42 | 43 | it 'no longer raises an error if member is an empty array' do 44 | expect(@redises.zrem(@key, [])).to eq 0 45 | end 46 | 47 | it_should_behave_like 'a zset-only command' 48 | end 49 | -------------------------------------------------------------------------------- /spec/commands/zremrangebyrank_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zremrangebyrank(key, start, stop)' do 4 | before do 5 | @key = 'mock-redis-test:zremrangebyrank' 6 | @redises.zadd(@key, 1, 'Washington') 7 | @redises.zadd(@key, 2, 'Adams') 8 | @redises.zadd(@key, 3, 'Jefferson') 9 | @redises.zadd(@key, 4, 'Madison') 10 | end 11 | 12 | it 'returns the number of elements in range' do 13 | expect(@redises.zremrangebyrank(@key, 2, 3)).to eq(2) 14 | end 15 | 16 | it 'removes the elements' do 17 | @redises.zremrangebyrank(@key, 2, 3) 18 | expect(@redises.zrange(@key, 0, -1)).to eq(%w[Washington Adams]) 19 | end 20 | 21 | it 'does nothing if start is greater than cardinality of set' do 22 | @redises.zremrangebyrank(@key, 5, -1) 23 | expect(@redises.zrange(@key, 0, -1)).to eq(%w[Washington Adams Jefferson Madison]) 24 | end 25 | 26 | it_should_behave_like 'a zset-only command' 27 | end 28 | -------------------------------------------------------------------------------- /spec/commands/zremrangebyscore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'date' 3 | 4 | RSpec.describe '#zremrangebyscore(key, min, max)' do 5 | before do 6 | @key = 'mock-redis-test:zremrangebyscore' 7 | @redises.zadd(@key, 1, 'Washington') 8 | @redises.zadd(@key, 2, 'Adams') 9 | @redises.zadd(@key, 3, 'Jefferson') 10 | @redises.zadd(@key, 4, 'Madison') 11 | end 12 | 13 | it 'returns the number of elements in range' do 14 | expect(@redises.zremrangebyscore(@key, 2, 3)).to eq(2) 15 | end 16 | 17 | it 'removes the elements' do 18 | @redises.zremrangebyscore(@key, 2, 3) 19 | expect(@redises.zrange(@key, 0, -1)).to eq(%w[Washington Madison]) 20 | end 21 | 22 | # As seen in http://redis.io/commands/zremrangebyscore 23 | it 'removes the elements for complex statements' do 24 | @redises.zremrangebyscore(@key, '-inf', '(4') 25 | expect(@redises.zrange(@key, 0, -1)).to eq(%w[Madison]) 26 | end 27 | 28 | it_should_behave_like 'a zset-only command' 29 | 30 | it 'throws a type error' do 31 | expect do 32 | @redises.zrevrangebyscore(@key, DateTime.now, '-inf') 33 | end.to raise_error(TypeError) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/commands/zrevrange_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zrevrange(key, start, stop [, :with_scores => true])' do 4 | before do 5 | @key = 'mock-redis-test:zrevrange' 6 | @redises.zadd(@key, 1, 'Washington') 7 | @redises.zadd(@key, 2, 'Adams') 8 | @redises.zadd(@key, 3, 'Jefferson') 9 | @redises.zadd(@key, 4, 'Madison') 10 | end 11 | 12 | context 'when the zset is empty' do 13 | before do 14 | @redises.del(@key) 15 | end 16 | 17 | it 'should return an empty array' do 18 | expect(@redises.exists?(@key)).to eq(false) 19 | expect(@redises.zrevrange(@key, 0, 4)).to eq([]) 20 | end 21 | end 22 | 23 | it 'returns the elements in order by score' do 24 | expect(@redises.zrevrange(@key, 0, 1)).to eq(%w[Madison Jefferson]) 25 | end 26 | 27 | context 'when a subset of elements have the same score' do 28 | before do 29 | @redises.zadd(@key, 1, 'Martha') 30 | end 31 | 32 | it 'returns the elements in descending lexicographical order' do 33 | expect(@redises.zrevrange(@key, 3, 4)).to eq(%w[Washington Martha]) 34 | end 35 | end 36 | 37 | it 'returns the elements in order by score (negative indices)' do 38 | expect(@redises.zrevrange(@key, -2, -1)).to eq(%w[Adams Washington]) 39 | end 40 | 41 | it 'returns empty list when start is too large' do 42 | expect(@redises.zrevrange(@key, 5, -1)).to eq([]) 43 | end 44 | 45 | it 'returns the scores when :with_scores is specified' do 46 | expect(@redises.zrevrange(@key, 2, 3, :with_scores => true)). 47 | to eq([['Adams', 2.0], ['Washington', 1.0]]) 48 | end 49 | 50 | it 'returns the scores when :withscores is specified' do 51 | expect(@redises.zrevrange(@key, 2, 3, :withscores => true)). 52 | to eq([['Adams', 2.0], ['Washington', 1.0]]) 53 | end 54 | 55 | it_should_behave_like 'a zset-only command' 56 | end 57 | -------------------------------------------------------------------------------- /spec/commands/zrevrangebyscore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zrevrangebyscore(key, start, stop '\ 4 | '[:with_scores => true] [:limit => [offset count]])' do 5 | before do 6 | @key = 'mock-redis-test:zrevrangebyscore' 7 | @redises.zadd(@key, 1, 'Washington') 8 | @redises.zadd(@key, 2, 'Adams') 9 | @redises.zadd(@key, 3, 'Jefferson') 10 | @redises.zadd(@key, 4, 'Madison') 11 | end 12 | 13 | context 'when the zset is empty' do 14 | before do 15 | @redises.del(@key) 16 | end 17 | 18 | it 'should return an empty array' do 19 | expect(@redises.exists?(@key)).to eq(false) 20 | expect(@redises.zrevrangebyscore(@key, 0, 4)).to eq([]) 21 | end 22 | end 23 | 24 | it 'returns the elements in order by score' do 25 | expect(@redises.zrevrangebyscore(@key, 4, 3)).to eq(%w[Madison Jefferson]) 26 | end 27 | 28 | it 'returns the scores when :with_scores is specified' do 29 | expect(@redises.zrevrangebyscore(@key, 4, 3, :with_scores => true)). 30 | to eq([['Madison', 4.0], ['Jefferson', 3.0]]) 31 | end 32 | 33 | it 'returns the scores when :withscores is specified' do 34 | expect(@redises.zrevrangebyscore(@key, 4, 3, :withscores => true)). 35 | to eq([['Madison', 4.0], ['Jefferson', 3.0]]) 36 | end 37 | 38 | it 'treats +inf as positive infinity' do 39 | expect(@redises.zrevrangebyscore(@key, '+inf', 3)). 40 | to eq(%w[Madison Jefferson]) 41 | end 42 | 43 | it 'honors the :limit => [offset count] argument' do 44 | expect(@redises.zrevrangebyscore(@key, 100, -100, :limit => [1, 2])). 45 | to eq(%w[Jefferson Adams]) 46 | end 47 | 48 | it "raises an error if :limit isn't a 2-tuple" do 49 | expect do 50 | @redises.zrevrangebyscore(@key, 100, -100, :limit => [1, 2, 3]) 51 | end.to raise_error(Redis::CommandError) 52 | 53 | expect do 54 | @redises.zrevrangebyscore(@key, 100, -100, :limit => '1, 2') 55 | end.to raise_error(RedisMultiplexer::MismatchedResponse) 56 | end 57 | 58 | it_should_behave_like 'a zset-only command' 59 | end 60 | -------------------------------------------------------------------------------- /spec/commands/zrevrank_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zrevrank(key, member)' do 4 | before do 5 | @key = 'mock-redis-test:zrevrank' 6 | 7 | @redises.zadd(@key, 1, 'one') 8 | @redises.zadd(@key, 2, 'two') 9 | @redises.zadd(@key, 3, 'three') 10 | end 11 | 12 | it "returns nil if member wasn't present in the set" do 13 | expect(@redises.zrevrank(@key, 'foo')).to be_nil 14 | end 15 | 16 | it 'returns the index of the member in the set (ordered by -score)' do 17 | expect(@redises.zrevrank(@key, 'one')).to eq(2) 18 | expect(@redises.zrevrank(@key, 'two')).to eq(1) 19 | expect(@redises.zrevrank(@key, 'three')).to eq(0) 20 | end 21 | 22 | it 'handles integer members correctly' do 23 | member = 11 24 | @redises.zadd(@key, 4, member) 25 | expect(@redises.zrevrank(@key, member)).to eq(0) 26 | end 27 | 28 | it_should_behave_like 'a zset-only command' 29 | end 30 | -------------------------------------------------------------------------------- /spec/commands/zscan_each_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zscan_each' do 4 | subject { MockRedis::Database.new(self) } 5 | 6 | let(:key) { 'mock-redis-test:zscan_each' } 7 | 8 | before do 9 | allow(subject).to receive(:zrange).and_return(collection) 10 | end 11 | 12 | context 'when no keys are found' do 13 | let(:collection) { [] } 14 | 15 | it 'does not iterate over any elements' do 16 | results = subject.zscan_each(key).map do |m, s| 17 | [m, s] 18 | end 19 | expect(results).to match_array(collection) 20 | end 21 | end 22 | 23 | context 'when keys are found' do 24 | context 'when no match filter is supplied' do 25 | let(:collection) { Array.new(20) { |i| ["m#{i}", i] } } 26 | 27 | it 'iterates over each item in the collection' do 28 | results = subject.zscan_each(key).map do |m, s| 29 | [m, s] 30 | end 31 | expect(results).to match_array(collection) 32 | end 33 | end 34 | 35 | context 'when giving a custom match filter' do 36 | let(:match) { 'm1*' } 37 | let(:collection) { Array.new(12) { |i| ["m#{i}", i] } } 38 | let(:expected) { [['m1', 1], ['m10', 10], ['m11', 11]] } 39 | 40 | it 'iterates over each item in the filtered collection' do 41 | results = subject.zscan_each(key, match: match).map do |m, s| 42 | [m, s] 43 | end 44 | expect(results).to match_array(expected) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/commands/zscan_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zscan' do 4 | let(:count) { 10 } 5 | let(:match) { '*' } 6 | let(:key) { 'mock-redis-test:zscan' } 7 | 8 | context 'when the zset does not exist' do 9 | it 'returns a 0 cursor and an empty collection' do 10 | expect(@redises.zscan(key, 0, count: count, match: match)).to eq(['0', []]) 11 | end 12 | end 13 | 14 | context 'when the zset exists' do 15 | before do 16 | @redises.zadd(key, 1.0, 'member1') 17 | @redises.zadd(key, 2.0, 'member2') 18 | end 19 | 20 | it 'returns a 0 cursor and the collection' do 21 | result = @redises.zscan(key, 0) 22 | expect(result[0]).to eq('0') 23 | expect(result[1]).to match_array([['member1', 1.0], ['member2', 2.0]]) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/commands/zscore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe '#zscore(key, member)' do 4 | before { @key = 'mock-redis-test:zscore' } 5 | 6 | it 'returns the score as a string' do 7 | expect(@redises.zadd(@key, 0.25, 'foo')).to eq(true) 8 | expect(@redises.zscore(@key, 'foo')).to eq(0.25) 9 | end 10 | 11 | it 'handles integer members correctly' do 12 | member = 11 13 | expect(@redises.zadd(@key, 0.25, member)).to eq(true) 14 | expect(@redises.zscore(@key, member)).to eq(0.25) 15 | end 16 | 17 | it 'returns nil if member is not present in the set' do 18 | expect(@redises.zscore(@key, 'foo')).to be_nil 19 | end 20 | 21 | it_should_behave_like 'a zset-only command' 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/shared_examples/does_not_cleanup_empty_strings.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'does not remove empty strings on error' do 2 | let(:method) { |example| method_from_description(example) } 3 | let(:args) { args_for_method(method) } 4 | let(:error) { defined?(default_error) ? default_error : Redis::WrongTypeError } 5 | 6 | it 'does not remove empty strings on error' do 7 | key = 'mock-redis-test:not-a-string' 8 | key_and_args = args.unshift(key) 9 | 10 | @redises.set(key, '') 11 | expect do 12 | @redises.send(method, *key_and_args) 13 | end.to raise_error(*error) 14 | expect(@redises.get(key)).to eq('') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/shared_examples/only_operates_on_hashes.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'a hash-only command' do 2 | it 'raises an error for non-hash values' do |example| 3 | key = 'mock-redis-test:hash-only' 4 | 5 | method = method_from_description(example) 6 | args = args_for_method(method).unshift(key) 7 | 8 | @redises.set(key, 1) 9 | expect do 10 | @redises.send(method, *args) 11 | end.to raise_error(Redis::WrongTypeError) 12 | end 13 | 14 | it_should_behave_like 'does not remove empty strings on error' 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/shared_examples/only_operates_on_lists.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'a list-only command' do 2 | let(:method) { |example| method_from_description(example) } 3 | let(:args) { args_for_method(method) } 4 | let(:error) { defined?(default_error) ? default_error : RuntimeError } 5 | 6 | it 'raises an error for non-list values' do 7 | key = 'mock-redis-test:list-only' 8 | key_and_args = args.unshift(key) 9 | 10 | @redises.set(key, 1) 11 | 12 | expect do 13 | @redises.send(method, *key_and_args) 14 | end.to raise_error(*error) 15 | end 16 | 17 | include_examples 'does not remove empty strings on error' 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/shared_examples/only_operates_on_sets.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'a set-only command' do 2 | it 'raises an error for non-set values' do |example| 3 | key = 'mock-redis-test:set-only' 4 | 5 | method = method_from_description(example) 6 | args = args_for_method(method).unshift(key) 7 | 8 | @redises.set(key, 1) 9 | expect do 10 | @redises.send(method, *args) 11 | end.to raise_error(Redis::WrongTypeError) 12 | end 13 | 14 | it_should_behave_like 'does not remove empty strings on error' 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/shared_examples/only_operates_on_strings.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'a string-only command' do |expected_error = Redis::WrongTypeError| 2 | it 'raises an error for non-string values' do |example| 3 | key = 'mock-redis-test:string-only-command' 4 | 5 | method = method_from_description(example) 6 | args = args_for_method(method).unshift(key) 7 | 8 | @redises.lpush(key, 1) 9 | expect do 10 | @redises.send(method, *args) 11 | end.to raise_error(expected_error) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/shared_examples/only_operates_on_zsets.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'a zset-only command' do 2 | it 'raises an error for non-zset values' do |example| 3 | key = 'mock-redis-test:zset-only' 4 | 5 | method = method_from_description(example) 6 | args = args_for_method(method).unshift(key) 7 | 8 | @redises.set(key, 1) 9 | expect do 10 | @redises.send(method, *args) 11 | end.to raise_error(Redis::WrongTypeError) 12 | end 13 | 14 | it_should_behave_like 'does not remove empty strings on error' 15 | end 16 | 17 | RSpec.shared_examples_for 'arg 1 is a score' do 18 | before { @_arg_index = 1 } 19 | it_should_behave_like 'arg N is a score' 20 | end 21 | 22 | RSpec.shared_examples_for 'arg 2 is a score' do 23 | before { @_arg_index = 2 } 24 | it_should_behave_like 'arg N is a score' 25 | end 26 | 27 | RSpec.shared_examples_for 'arg N is a score' do 28 | before do |example| 29 | key = 'mock-redis-test:zset-only' 30 | 31 | @method = method_from_description(example) 32 | @args = args_for_method(@method).unshift(key) 33 | end 34 | 35 | it 'is okay with positive ints' do 36 | @args[@_arg_index] = 1 37 | expect { @redises.send(@method, *@args) }.not_to raise_error 38 | end 39 | 40 | it 'is okay with negative ints' do 41 | @args[@_arg_index] = -1 42 | expect { @redises.send(@method, *@args) }.not_to raise_error 43 | end 44 | 45 | it 'is okay with positive floats' do 46 | @args[@_arg_index] = 1.5 47 | expect { @redises.send(@method, *@args) }.not_to raise_error 48 | end 49 | 50 | it 'is okay with negative floats' do 51 | @args[@_arg_index] = -1.5 52 | expect { @redises.send(@method, *@args) }.not_to raise_error 53 | end 54 | 55 | it 'rejects non-numbers' do 56 | @args[@_arg_index] = 'foo' 57 | expect { @redises.send(@method, *@args) }.to raise_error(Redis::CommandError) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/support/shared_examples/raises_on_invalid_expire_command_options.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'raises on invalid expire command options' do |command| 2 | [%i[nx xx], %i[nx lt], %i[nx gt], %i[lt gt]].each do |options| 3 | context "with `#{options[0]}` and `#{options[1]}` options" do 4 | it 'raises `Redis::CommandError`' do 5 | expect { @mock.public_send(command, @key, 1, **options.zip([true, true]).to_h) } 6 | .to raise_error( 7 | Redis::CommandError, 8 | /ERR NX and XX, GT or LT options at the same time are not compatible/ 9 | ) 10 | end 11 | end 12 | 13 | context 'with unexpected key' do 14 | it 'raises `ArgumentError`' do 15 | expect { @mock.public_send(command, @key, 1, foo: true) } 16 | .to raise_error(ArgumentError) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/shared_examples/sorts_enumerables.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'a sortable' do 2 | it 'returns empty array on nil' do 3 | expect { @redises.sort(nil) }.to raise_error(TypeError) 4 | end 5 | 6 | context 'ordering' do 7 | it 'orders ascending by default' do 8 | expect(@redises.sort(@key)).to eq(%w[1 2]) 9 | end 10 | 11 | it 'orders by descending when specified' do 12 | expect(@redises.sort(@key, :order => 'DESC')).to eq(%w[2 1]) 13 | end 14 | end 15 | 16 | context 'projections' do 17 | it 'projects element when :get is "#"' do 18 | expect(@redises.sort(@key, :get => '#')).to eq(%w[1 2]) 19 | end 20 | 21 | it 'projects through a key pattern' do 22 | expect(@redises.sort(@key, :get => 'mock-redis-test:values_*')).to eq(%w[a b]) 23 | end 24 | 25 | it 'projects through a key pattern and reflects element' do 26 | expect(@redises.sort(@key, :get => ['#', 'mock-redis-test:values_*'])) 27 | .to eq([%w[1 a], %w[2 b]]) 28 | end 29 | 30 | it 'projects through a hash key pattern' do 31 | expect(@redises.sort(@key, :get => 'mock-redis-test:hash_*->key')).to eq(%w[x y]) 32 | end 33 | end 34 | 35 | context 'weights' do 36 | it 'weights by projecting through a key pattern' do 37 | expect(@redises.sort(@key, :by => 'mock-redis-test:weight_*')).to eq(%w[2 1]) 38 | end 39 | 40 | it 'weights by projecting through a key pattern and a specific order' do 41 | expect(@redises.sort(@key, :order => 'DESC', :by => 'mock-redis-test:weight_*')) 42 | .to eq(%w[1 2]) 43 | end 44 | end 45 | 46 | context 'limit' do 47 | it 'only returns requested window in the enumerable' do 48 | expect(@redises.sort(@key, :limit => [0, 1])).to eq(['1']) 49 | end 50 | end 51 | 52 | context 'store' do 53 | it 'stores into another key' do 54 | expect(@redises.sort(@key, :store => 'mock-redis-test:some_bucket')).to eq(2) 55 | expect(@redises.lrange('mock-redis-test:some_bucket', 0, -1)).to eq(%w[1 2]) 56 | end 57 | end 58 | end 59 | --------------------------------------------------------------------------------