text: // TODO: These are really a mix of unit and integration tests.
  
  var assert = require('assert');
  var async = require('async');
  var sinon = require('sinon');
  var support = require('./support');
  var check_err = support.check_err;
  var caching = require('../index').caching;
  var memory_store = require('../lib/stores/memory');
  
  var methods = {
      get_widget: function (name, cb) {
          cb(null, {name: name});
      }
  };
  
  describe("caching", function () {
      var cache;
      var key;
      var ttl = 1;
      var name;
      var value;
  
      describe("get() and set()", function () {
          ['memory'].forEach(function (store) {
              context("using " + store + " store", function () {
                  beforeEach(function () {
                      cache = caching({store: store});
                      key = support.random.string(20);
                      value = support.random.string();
                  });
  
                  it("lets us set and get data in cache", function (done) {
                      cache.set(key, value, ttl, function (err) {
                          check_err(err);
                          cache.get(key, function (err, result) {
                              assert.equal(result, value);
                              done();
                          });
                      });
                  });
  
                  it("lets us set and get data without a callback", function (done) {
                      cache.set(key, value, ttl);
                      setTimeout(function () {
                          var result = cache.get(key);
                          assert.equal(result, value);
                          done();
                      }, 20);
                  });
  
                  it("lets us set and get data without a ttl or callback", function (done) {
                      cache.set(key, value);
                      setTimeout(function () {
                          var result = cache.get(key);
                          assert.equal(result, value);
                          done();
                      }, 20);
                  });
              });
          });
      });
  
      describe("del()", function () {
          ['memory'].forEach(function (store) {
              context("using " + store + " store", function () {
                  beforeEach(function (done) {
                      cache = caching({store: store});
                      key = support.random.string(20);
                      value = support.random.string();
                      cache.set(key, value, ttl, function (err) {
                          check_err(err);
                          done();
                      });
                  });
  
                  it("deletes data from cache", function (done) {
                      cache.get(key, function (err, result) {
                          assert.equal(result, value);
  
                          cache.del(key, function (err) {
                              check_err(err);
  
                              cache.get(key, function (err, result) {
                                  assert.ok(!result);
                                  done();
                              });
                          });
                      });
                  });
  
                  it("lets us delete data without a callback", function (done) {
                      cache.get(key, function (err, result) {
                          assert.equal(result, value);
  
                          cache.del(key);
  
                          setTimeout(function () {
                              cache.get(key, function (err, result) {
                                  assert.ok(!result);
                                  done();
                              });
                          }, 20);
                      });
                  });
              });
          });
      });
  
      describe("reset()", function () {
          var key2;
          var value2;
  
          beforeEach(function (done) {
              cache = caching({store: 'memory'});
              key = support.random.string(20);
              value = support.random.string();
              cache.set(key, value, ttl, function (err) {
                  check_err(err);
  
                  key2 = support.random.string(20);
                  value2 = support.random.string();
  
                  cache.set(key2, value2, ttl, done);
              });
          });
  
          it("clears the cache", function (done) {
              cache.reset(function (err) {
                  check_err(err);
  
                  cache.get(key, function (err, result) {
                      assert.ok(!result);
  
                      cache.get(key2, function (err, result) {
                          assert.ok(!result);
                          done();
                      });
                  });
              });
          });
  
          it("lets us clear the cache without a callback (memory store only)", function (done) {
              cache.reset();
              setTimeout(function () {
                  cache.get(key, function (err, result) {
                      assert.ok(!result);
  
                      cache.get(key2, function (err, result) {
                          assert.ok(!result);
                          done();
                      });
                  });
              }, 10);
          });
  
          context("when store has no del() method", function () {
              var fake_store;
  
              beforeEach(function () {
                  fake_store = {
                      get: function () {},
                      set: function () {},
                  };
              });
  
              it("it doesn't throw an error", function () {
                  assert.doesNotThrow(function () {
                      caching({store: fake_store});
                  });
              });
          });
      });
  
      describe("setex()", function () {
          var fake_store;
  
          beforeEach(function () {
              fake_store = {
                  get: function () {},
                  set: function () {},
                  del: function () {},
                  setex: function () {}
              };
  
              sinon.stub(fake_store, 'setex');
  
              cache = caching({store: fake_store});
          });
  
          it("passes the params to the underlying store's setex() method", function () {
              cache.setex('foo', 'bar', 'blah');
              assert.ok(fake_store.setex.calledWith('foo', 'bar', 'blah'));
          });
      });
  
      describe("keys()", function () {
          var key_count;
          var saved_keys = [];
  
          beforeEach(function (done) {
              key_count = 10;
              var processed = 0;
  
              cache = caching({store: 'memory'});
  
              function is_done() {
                  return processed === key_count;
              }
  
              async.until(is_done, function (cb) {
                  processed += 1;
                  key = support.random.string(20);
                  saved_keys.push(key);
                  value = support.random.string();
                  cache.set(key, value, ttl, cb);
              }, done);
          });
  
          it("calls back with all keys in cache", function (done) {
              cache.keys(function (err, keys) {
                  check_err(err);
                  assert.deepEqual(keys.sort, saved_keys.sort);
                  done();
              });
          });
  
          it("lets us get the keys without a callback (memory store only)", function () {
              var keys = cache.keys();
              assert.deepEqual(keys.sort, saved_keys.sort);
          });
      });
  
      describe("wrap()", function () {
          describe("using memory (lru-cache) store", function () {
              var memory_store_stub;
  
              beforeEach(function () {
                  ttl = 0.1;
                  memory_store_stub = memory_store.create({ttl: ttl});
  
                  sinon.stub(memory_store, 'create').returns(memory_store_stub);
  
                  cache = caching({store: 'memory', ttl: ttl, ignoreCacheErrors: false});
                  key = support.random.string(20);
                  name = support.random.string();
              });
  
              afterEach(function () {
                  memory_store.create.restore();
              });
  
              context("calls back with the result of the wrapped function", function () {
                  beforeEach(function () {
                      sinon.spy(memory_store_stub, 'set');
                  });
  
                  afterEach(function () {
                      memory_store_stub.set.restore();
                  });
  
                  it("when a ttl is passed in", function (done) {
                      cache.wrap(key, function (cb) {
                          methods.get_widget(name, cb);
                      }, ttl, function (err, widget) {
                          check_err(err);
                          assert.deepEqual(widget, {name: name});
                          sinon.assert.calledWith(memory_store_stub.set, key, {name: name}, ttl);
                          done();
                      });
                  });
  
                  it("when a ttl is not passed in", function (done) {
                      cache.wrap(key, function (cb) {
                          methods.get_widget(name, cb);
                      }, function (err, widget) {
                          check_err(err);
                          assert.deepEqual(widget, {name: name});
                          sinon.assert.calledWith(memory_store_stub.set, key, {name: name}, undefined);
                          done();
                      });
                  });
              });
  
              context("when result is already cached", function () {
                  function get_cached_widget(name, cb) {
                      cache.wrap(key, function (cache_cb) {
                          methods.get_widget(name, cache_cb);
                      }, ttl, cb);
                  }
  
                  beforeEach(function (done) {
                      get_cached_widget(name, function (err, widget) {
                          check_err(err);
                          assert.ok(widget);
  
                          memory_store_stub.get(key, function (err, result) {
                              check_err(err);
                              assert.ok(result);
  
                              sinon.spy(memory_store_stub, 'get');
  
                              done();
                          });
                      });
                  });
  
                  afterEach(function () {
                      memory_store_stub.get.restore();
                  });
  
                  it("retrieves data from cache", function (done) {
                      var func_called = false;
  
                      cache.wrap(key, function (cb) {
                          methods.get_widget(name, function (err, result) {
                              func_called = true;
                              cb(err, result);
                          });
                      }, ttl, function (err, widget) {
                          check_err(err);
                          assert.deepEqual(widget, {name: name});
                          assert.ok(memory_store_stub.get.calledWith(key));
                          assert.ok(!func_called);
                          done();
                      });
                  });
              });
  
              it("expires cached result after ttl seconds", function (done) {
                  cache.wrap(key, function (cb) {
                      methods.get_widget(name, cb);
                  }, ttl, function (err, widget) {
                      check_err(err);
                      assert.deepEqual(widget, {name: name});
  
                      memory_store_stub.get(key, function (err, result) {
                          check_err(err);
                          assert.ok(result);
  
                          var func_called = false;
  
                          setTimeout(function () {
                              cache.wrap(key, function (cb) {
                                  methods.get_widget(name, function (err, result) {
                                      func_called = true;
                                      cb(err, result);
                                  });
                              }, function (err, widget) {
                                  check_err(err);
                                  assert.ok(func_called);
                                  assert.deepEqual(widget, {name: name});
                                  done();
                              });
                          }, (ttl * 1000 + 10));
                      });
                  });
              });
  
              context("when store.get() calls back with an error", function () {
                  context("and ignoreCacheErrors is not set (default is false)", function () {
                      it("bubbles up that error", function (done) {
                          var fake_error = new Error(support.random.string());
  
                          sinon.stub(memory_store_stub, 'get', function (key, cb) {
                              cb(fake_error);
                          });
  
                          cache.wrap(key, function (cb) {
                              methods.get_widget(name, cb);
                          }, ttl, function (err) {
                              assert.equal(err, fake_error);
                              memory_store_stub.get.restore();
                              done();
                          });
                      });
                  });
  
                  context("and ignoreCacheErrors is set to true", function () {
                      it("does not bubble up that error", function (done) {
                          cache = caching({store: 'memory', ttl: ttl, ignoreCacheErrors: true});
  
                          var fake_error = new Error(support.random.string());
  
                          sinon.stub(memory_store_stub, 'get', function (key, cb) {
                              cb(fake_error);
                          });
  
                          cache.wrap(key, function (cb) {
                              methods.get_widget(name, cb);
                          }, ttl, function (err) {
                              assert.equal(err, null);
                              memory_store_stub.get.restore();
                              done();
                          });
                      });
                  });
              });
  
              context("when store.set() calls back with an error", function () {
                  context("and ignoreCacheErrors is not set", function () {
                      it("bubbles up that error", function (done) {
                          var fake_error = new Error(support.random.string());
  
                          sinon.stub(memory_store_stub, 'set', function (key, val, ttl, cb) {
                              cb(fake_error);
                          });
  
                          cache.wrap(key, function (cb) {
                              methods.get_widget(name, cb);
                          }, ttl, function (err) {
                              assert.equal(err, fake_error);
                              memory_store_stub.set.restore();
                              done();
                          });
                      });
                  });
  
                  context("and ignoreCacheErrors is set to true", function () {
                      it("does not bubbles up that error", function (done) {
                          cache = caching({store: 'memory', ttl: ttl, ignoreCacheErrors: true});
                          var fake_error = new Error(support.random.string());
  
                          sinon.stub(memory_store_stub, 'set', function (key, val, ttl, cb) {
                              cb(fake_error);
                          });
  
                          cache.wrap(key, function (cb) {
                              methods.get_widget(name, cb);
                          }, ttl, function (err) {
                              assert.equal(err, null);
                              memory_store_stub.set.restore();
                              done();
                          });
                      });
                  });
              });
  
              context("when wrapped function calls back with an error", function () {
                  it("calls back with that error", function (done) {
                      var fake_error = new Error(support.random.string());
                      sinon.stub(methods, 'get_widget', function (name, cb) {
                          cb(fake_error, {name: name});
                      });
  
                      cache.wrap(key, function (cb) {
                          methods.get_widget(name, cb);
                      }, ttl, function (err, widget) {
                          methods.get_widget.restore();
                          assert.equal(err, fake_error);
                          assert.ok(!widget);
                          done();
                      });
                  });
              });
          });
  
          describe("when called multiple times in parallel with same key", function () {
              var construct;
  
              beforeEach(function () {
                  cache = caching({
                      store: 'memory',
                      max: 50,
                      ttl: 5 * 60
                  });
  
                  construct = sinon.spy(function (val, cb) {
                      var timeout = support.random.number(100);
                      setTimeout(function () {
                          cb(null, 'value');
                      }, timeout);
                  });
              });
  
              it("calls the wrapped function once", function (done) {
                  var values = [];
                  for (var i = 0; i < 2; i++) {
                      values.push(i);
                  }
  
                  async.each(values, function (val, async_cb) {
                      cache.wrap('key', function (cb) {
                          construct(val, cb);
                      }, ttl, function (err, result) {
                          assert.equal(result, 'value');
                          async_cb(err);
                      });
                  }, function (err) {
                      check_err(err);
                      assert.equal(construct.callCount, 1);
                      done();
                  });
              });
          });
      });
  
      describe("instantiating with no store passed in", function () {
          it("defaults to 'memory' store", function () {
              var cache = caching();
              assert.equal(cache.store.name, 'memory');
          });
      });
  
      describe("instantiating with custom store", function () {
          it("allows us to pass in our own store object", function (done) {
              var store = memory_store.create({ttl: ttl});
              cache = caching({store: store});
              cache.set(key, value, ttl, function (err) {
                  check_err(err);
                  cache.get(key, function (err, result) {
                      assert.equal(result, value);
                      done();
                  });
              });
          });
  
          it("allows us to pass in a path to our own store", function (done) {
              var store_path = '../lib/stores/memory';
              cache = caching({store: store_path});
              cache.set(key, value, ttl, function (err) {
                  check_err(err);
                  cache.get(key, function (err, result) {
                      assert.equal(result, value);
                      done();
                  });
              });
          });
  
          it("allows us to pass in a module (uninstantiated)", function (done) {
              var store = memory_store;
              cache = caching({store: store});
              cache.set(key, value, ttl, function (err) {
                  check_err(err);
                  cache.get(key, function (err, result) {
                      assert.equal(result, value);
                      done();
                  });
              });
          });
      });
  });
  