summaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
authorAzul <azul@riseup.net>2016-03-23 21:48:52 +0100
committerAzul <azul@riseup.net>2016-05-02 08:21:52 -0300
commit065859b90cc5ef403b8f47bd5394b343e556cc4d (patch)
treede62cd7a23b2db2a620911c6d861a7fda1d74a00 /vendor
parentabf600089a4bc4b5c7271e17b1fd95a95deb293f (diff)
upgrade: remove references to RestClient
CouchRest > 1.2 does not use RestClient anymore. So we should not try to catch its errors.
Diffstat (limited to 'vendor')
-rw-r--r--vendor/gems/couchrest_session_store/lib/couchrest/model/database_method.rb131
-rw-r--r--vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb263
-rw-r--r--vendor/gems/couchrest_session_store/lib/couchrest/session/document.rb119
-rw-r--r--vendor/gems/couchrest_session_store/lib/couchrest/session/store.rb94
-rw-r--r--vendor/gems/couchrest_session_store/test/database_method_test.rb116
-rw-r--r--vendor/gems/couchrest_session_store/test/session_store_test.rb168
6 files changed, 891 insertions, 0 deletions
diff --git a/vendor/gems/couchrest_session_store/lib/couchrest/model/database_method.rb b/vendor/gems/couchrest_session_store/lib/couchrest/model/database_method.rb
new file mode 100644
index 0000000..6ecc8f3
--- /dev/null
+++ b/vendor/gems/couchrest_session_store/lib/couchrest/model/database_method.rb
@@ -0,0 +1,131 @@
+#
+# Allow setting the database to happen dynamically.
+#
+# Unlike normal CouchRest::Model, the database is not automatically created
+# unless you call database!()
+#
+# The method specified by `database_method` must exist as a class method but
+# may optionally also exist as an instance method.
+#
+
+module CouchRest
+ module Model
+ module DatabaseMethod
+ extend ActiveSupport::Concern
+
+ def database
+ if self.class.database_method
+ self.class.server.database(call_database_method)
+ else
+ self.class.database
+ end
+ end
+
+ def database!
+ if self.class.database_method
+ self.class.server.database!(call_database_method)
+ else
+ self.class.database!
+ end
+ end
+
+ def database_exists?(db_name)
+ self.class.database_exists?(db_name)
+ end
+
+ #
+ # The normal CouchRest::Model::Base comparison checks if the model's
+ # database objects are the same. That is not good for use here, since
+ # the objects will always be different. Instead, we compare the string
+ # that each database evaluates to.
+ #
+ def ==(other)
+ return false unless other.is_a?(Base)
+ if id.nil? && other.id.nil?
+ to_hash == other.to_hash
+ else
+ id == other.id && database.to_s == other.database.to_s
+ end
+ end
+ alias :eql? :==
+
+ protected
+
+ def call_database_method
+ if self.respond_to?(self.class.database_method)
+ name = self.send(self.class.database_method)
+ self.class.db_name_with_prefix(name)
+ else
+ self.class.send(:call_database_method)
+ end
+ end
+
+ module ClassMethods
+
+ def database_method(method = nil)
+ if method
+ @database_method = method
+ end
+ @database_method
+ end
+ alias :use_database_method :database_method
+
+ def database
+ if database_method
+ if !self.respond_to?(database_method)
+ raise ArgumentError.new("Incorrect argument to database_method(): no such method '#{method}' found in class #{self}.")
+ end
+ self.server.database(call_database_method)
+ else
+ @database ||= prepare_database(super)
+ end
+ end
+
+ def database!
+ if database_method
+ self.server.database!(call_database_method)
+ else
+ @database ||= prepare_database(super)
+ end
+ end
+
+ #
+ # same as database(), but allows for an argument that gets passed through to
+ # database method.
+ #
+ def choose_database(*args)
+ self.server.database(call_database_method(*args))
+ end
+
+ def db_name_with_prefix(name)
+ conf = self.send(:connection_configuration)
+ [conf[:prefix], name, conf[:suffix]].reject{|i|i.to_s.empty?}.join(conf[:join])
+ end
+
+ def database_exists?(name)
+ name = db_name_with_prefix(name)
+ begin
+ CouchRest.head "#{self.server.uri}/#{name}"
+ return true
+ rescue CouchRest::NotFound
+ return false
+ end
+ end
+
+ protected
+
+ def call_database_method(*args)
+ name = nil
+ method = self.method(database_method)
+ if method.arity == 0
+ name = method.call
+ else
+ name = method.call(*args)
+ end
+ db_name_with_prefix(name)
+ end
+
+ end
+ end
+ end
+end
diff --git a/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb b/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb
new file mode 100644
index 0000000..9e1a5c3
--- /dev/null
+++ b/vendor/gems/couchrest_session_store/lib/couchrest/model/rotation.rb
@@ -0,0 +1,263 @@
+module CouchRest
+ module Model
+ module Rotation
+ extend ActiveSupport::Concern
+ include CouchRest::Model::DatabaseMethod
+
+ included do
+ use_database_method :rotated_database_name
+ end
+
+ def create(*args)
+ super(*args)
+ rescue CouchRest::NotFound => exc
+ raise storage_missing(exc)
+ end
+
+ def update(*args)
+ super(*args)
+ rescue CouchRest::NotFound => exc
+ raise storage_missing(exc)
+ end
+
+ def destroy(*args)
+ super(*args)
+ rescue CouchRest::NotFound => exc
+ raise storage_missing(exc)
+ end
+
+ private
+
+ # returns a special 'storage missing' exception when the db has
+ # not been created. very useful, since this happens a lot and a
+ # generic 404 is not that helpful.
+ def storage_missing(exc)
+ if exc.http_body =~ /no_db_file/
+ CouchRest::StorageMissing.new(exc.response, database)
+ else
+ exc
+ end
+ end
+
+ public
+
+ module ClassMethods
+ #
+ # Set up database rotation.
+ #
+ # base_name -- the name of the db before the rotation number is
+ # appended.
+ #
+ # options -- one of:
+ #
+ # * :every -- frequency of rotation
+ # * :expiration_field - what field to use to determine if a
+ # document is expired.
+ # * :timestamp_field - alternately, what field to use for the
+ # document timestamp.
+ # * :timeout -- used to expire documents with only a timestamp
+ # field (in minutes)
+ #
+ def rotate_database(base_name, options={})
+ @rotation_base_name = base_name
+ @rotation_every = (options.delete(:every) || 30.days).to_i
+ @expiration_field = options.delete(:expiration_field)
+ @timestamp_field = options.delete(:timestamp_field)
+ @timeout = options.delete(:timeout)
+ if options.any?
+ raise ArgumentError.new('Could not understand options %s' % options.keys)
+ end
+ end
+
+ #
+ # Check to see if dbs should be rotated. The :window
+ # argument specifies how far in advance we should
+ # create the new database (default 1.day).
+ #
+ # This method relies on the assumption that it is called
+ # at least once within each @rotation_every period.
+ #
+ def rotate_database_now(options={})
+ window = options[:window] || 1.day
+
+ now = Time.now.utc
+ current_name = rotated_database_name(now)
+ current_count = now.to_i/@rotation_every
+
+ next_time = window.from_now.utc
+ next_name = rotated_database_name(next_time)
+ next_count = current_count+1
+
+ prev_name = current_name.sub(/(\d+)$/) {|i| i.to_i-1}
+ replication_started = false
+ old_name = prev_name.sub(/(\d+)$/) {|i| i.to_i-1} # even older than prev_name
+ trailing_edge_time = window.ago.utc
+
+ if !database_exists?(current_name)
+ # we should have created the current db earlier, but if somehow
+ # it is missing we must make sure it exists.
+ create_new_rotated_database(:from => prev_name, :to => current_name)
+ replication_started = true
+ end
+
+ if next_time.to_i/@rotation_every >= next_count && !database_exists?(next_name)
+ # time to create the next db in advance of actually needing it.
+ create_new_rotated_database(:from => current_name, :to => next_name)
+ end
+
+ if trailing_edge_time.to_i/@rotation_every == current_count
+ # delete old dbs, but only after window time has past since the last rotation
+ if !replication_started && database_exists?(prev_name)
+ # delete previous, but only if we didn't just start replicating from it
+ self.server.database(db_name_with_prefix(prev_name)).delete!
+ end
+ if database_exists?(old_name)
+ # there are some edge cases, when rotate_database_now is run
+ # infrequently, that an older db might be left around.
+ self.server.database(db_name_with_prefix(old_name)).delete!
+ end
+ end
+ end
+
+ def rotated_database_name(time=nil)
+ unless @rotation_base_name && @rotation_every
+ raise ArgumentError.new('missing @rotation_base_name or @rotation_every')
+ end
+ time ||= Time.now.utc
+ units = time.to_i / @rotation_every.to_i
+ "#{@rotation_base_name}_#{units}"
+ end
+
+ #
+ # create a new empty database.
+ #
+ def create_database!(name=nil)
+ db = if name
+ self.server.database!(db_name_with_prefix(name))
+ else
+ self.database!
+ end
+ create_rotation_filter(db)
+ if self.respond_to?(:design_doc)
+ design_doc.sync!(db)
+ # or maybe this?:
+ #self.design_docs.each do |design|
+ # design.migrate(to_db)
+ #end
+ end
+ return db
+ end
+
+ protected
+
+ #
+ # Creates database named by options[:to]. Optionally, set up
+ # continuous replication from the options[:from] db, if it exists. The
+ # assumption is that the from db will be destroyed later, cleaning up
+ # the replication once it is no longer needed.
+ #
+ # This method will also copy design documents if present in the from
+ # db, in the CouchRest::Model, or in a database named after
+ # @rotation_base_name.
+ #
+ def create_new_rotated_database(options={})
+ from = options[:from]
+ to = options[:to]
+ to_db = self.create_database!(to)
+ if database_exists?(@rotation_base_name)
+ base_db = self.server.database(db_name_with_prefix(@rotation_base_name))
+ copy_design_docs(base_db, to_db)
+ end
+ if from && from != to && database_exists?(from)
+ from_db = self.server.database(db_name_with_prefix(from))
+ replicate_old_to_new(from_db, to_db)
+ end
+ end
+
+ def copy_design_docs(from, to)
+ params = {:startkey => '_design/', :endkey => '_design0', :include_docs => true}
+ from.documents(params) do |doc_hash|
+ design = doc_hash['doc']
+ begin
+ to.get(design['_id'])
+ rescue CouchRest::NotFound
+ design.delete('_rev')
+ to.save_doc(design)
+ end
+ end
+ end
+
+ def create_rotation_filter(db)
+ name = 'rotation_filter'
+ filter_string = if @expiration_field
+ NOT_EXPIRED_FILTER % {:expires => @expiration_field}
+ elsif @timestamp_field && @timeout
+ NOT_TIMED_OUT_FILTER % {:timestamp => @timestamp_field, :timeout => (60 * @timeout)}
+ else
+ NOT_DELETED_FILTER
+ end
+ filters = {"not_expired" => filter_string}
+ db.save_doc("_id" => "_design/#{name}", "filters" => filters)
+ rescue CouchRest::Conflict
+ end
+
+ #
+ # Replicates documents from_db to to_db, skipping documents that have
+ # expired or been deleted.
+ #
+ # NOTE: It would be better if we could do this:
+ #
+ # from_db.replicate_to(to_db, true, false,
+ # :filter => 'rotation_filter/not_expired')
+ #
+ # But replicate_to() does not support a filter argument, so we call
+ # the private method replication() directly.
+ #
+ def replicate_old_to_new(from_db, to_db)
+ create_rotation_filter(from_db)
+ from_db.send(:replicate, to_db, true, :source => from_db.name, :filter => 'rotation_filter/not_expired')
+ end
+
+ #
+ # Three different filters, depending on how the model is set up.
+ #
+ # NOT_EXPIRED_FILTER is used when there is a single field that
+ # contains an absolute time for when the document has expired. The
+ #
+ # NOT_TIMED_OUT_FILTER is used when there is a field that records the
+ # timestamp of the last time the document was used. The expiration in
+ # this case is calculated from the timestamp plus @timeout.
+ #
+ # NOT_DELETED_FILTER is used when the other two cannot be.
+ #
+ NOT_EXPIRED_FILTER = "" +
+%[function(doc, req) {
+ if (doc._deleted) {
+ return false;
+ } else if (typeof(doc.%{expires}) != "undefined") {
+ return Date.now() < (new Date(doc.%{expires})).getTime();
+ } else {
+ return true;
+ }
+}]
+
+ NOT_TIMED_OUT_FILTER = "" +
+%[function(doc, req) {
+ if (doc._deleted) {
+ return false;
+ } else if (typeof(doc.%{timestamp}) != "undefined") {
+ return Date.now() < (new Date(doc.%{timestamp})).getTime() + %{timeout};
+ } else {
+ return true;
+ }
+}]
+
+ NOT_DELETED_FILTER = "" +
+%[function(doc, req) {
+ return !doc._deleted;
+}]
+
+ end
+ end
+ end
+end
diff --git a/vendor/gems/couchrest_session_store/lib/couchrest/session/document.rb b/vendor/gems/couchrest_session_store/lib/couchrest/session/document.rb
new file mode 100644
index 0000000..b1e73cc
--- /dev/null
+++ b/vendor/gems/couchrest_session_store/lib/couchrest/session/document.rb
@@ -0,0 +1,119 @@
+require 'couchrest/session/utility'
+require 'time'
+
+class CouchRest::Session::Document < CouchRest::Document
+ include CouchRest::Model::Configuration
+ include CouchRest::Model::Connection
+ include CouchRest::Session::Utility
+ include CouchRest::Model::Rotation
+
+ rotate_database 'sessions',
+ :every => 1.month, :expiration_field => :expires
+
+ def self.fetch(sid)
+ self.allocate.tap do |session_doc|
+ session_doc.fetch(sid)
+ end
+ end
+
+ def self.build(sid, session, options = {})
+ self.new(CouchRest::Document.new({"_id" => sid})).tap do |session_doc|
+ session_doc.update session, options
+ end
+ end
+
+ def self.build_or_update(sid, session, options = {})
+ options[:marshal_data] = true if options[:marshal_data].nil?
+ doc = self.fetch(sid)
+ doc.update(session, options)
+ return doc
+ rescue CouchRest::NotFound
+ self.build(sid, session, options)
+ end
+
+ def self.find_by_expires(options = {})
+ options[:reduce] ||= false
+ design = database.get '_design/Session'
+ response = design.view :by_expires, options
+ response['rows']
+ end
+
+ def self.create_database!(name=nil)
+ db = super(name)
+ begin
+ db.get('_design/Session')
+ rescue CouchRest::NotFound
+ design = File.read(File.expand_path('../../../../design/Session.json', __FILE__))
+ design = JSON.parse(design)
+ db.save_doc(design.merge({"_id" => "_design/Session"}))
+ end
+ db
+ end
+
+ def initialize(doc)
+ @doc = doc
+ end
+
+ def fetch(sid = nil)
+ @doc = database.get(sid || doc['_id'])
+ end
+
+ def to_session
+ if doc["marshalled"]
+ session = unmarshal(doc["data"])
+ else
+ session = doc["data"]
+ end
+ return session
+ end
+
+ def delete
+ database.delete_doc(doc)
+ end
+
+ def update(session, options)
+ # clean up old data but leave id and revision intact
+ doc.reject! do |k,v|
+ k[0] != '_'
+ end
+ doc.merge! data_for_doc(session, options)
+ end
+
+ def save
+ database.save_doc(doc)
+ rescue CouchRest::Conflict
+ fetch
+ retry
+ rescue CouchRest::NotFound => exc
+ if exc.http_body =~ /no_db_file/
+ exc = CouchRest::StorageMissing.new(exc.response, database)
+ end
+ raise exc
+ end
+
+ def expired?
+ expires && expires < Time.now
+ end
+
+ protected
+
+ def data_for_doc(session, options)
+ { "data" => options[:marshal_data] ? marshal(session) : session,
+ "marshalled" => options[:marshal_data],
+ "expires" => expiry_from_options(options) }
+ end
+
+ def expiry_from_options(options)
+ expire_after = options[:expire_after]
+ expire_after && (Time.now + expire_after).utc
+ end
+
+ def expires
+ doc["expires"] && Time.iso8601(doc["expires"])
+ end
+
+ def doc
+ @doc
+ end
+
+end
diff --git a/vendor/gems/couchrest_session_store/lib/couchrest/session/store.rb b/vendor/gems/couchrest_session_store/lib/couchrest/session/store.rb
new file mode 100644
index 0000000..f209f54
--- /dev/null
+++ b/vendor/gems/couchrest_session_store/lib/couchrest/session/store.rb
@@ -0,0 +1,94 @@
+class CouchRest::Session::Store < ActionDispatch::Session::AbstractStore
+
+ # delegate configure to document
+ def self.configure(*args, &block)
+ CouchRest::Session::Document.configure *args, &block
+ end
+
+ def self.set_options(options)
+ @options = options
+ if @options[:database]
+ CouchRest::Session::Document.use_database @options[:database]
+ end
+ end
+
+ def initialize(app, options = {})
+ super
+ self.class.set_options(options)
+ end
+
+ def cleanup(rows)
+ rows.each do |row|
+ doc = CouchRest::Session::Document.fetch(row['id'])
+ doc.delete
+ end
+ end
+
+ def expired
+ CouchRest::Session::Document.find_by_expires startkey: 1,
+ endkey: Time.now.utc.iso8601
+ end
+
+ def never_expiring
+ CouchRest::Session::Document.find_by_expires endkey: 1
+ end
+
+ private
+
+ def get_session(env, sid)
+ if session = fetch_session(sid)
+ [sid, session]
+ else
+ [generate_sid, {}]
+ end
+ rescue CouchRest::NotFound
+ # session data does not exist anymore
+ return [sid, {}]
+ rescue CouchRest::Unauthorized,
+ Errno::EHOSTUNREACH,
+ Errno::ECONNREFUSED => e
+ # can't connect to couch. We add some status to the session
+ # so the app can react. (Display error for example)
+ return [sid, {"_status" => {"couch" => "unreachable"}}]
+ end
+
+ def set_session(env, sid, session, options)
+ raise CouchRest::NotFound if /^_design\/(.*)/ =~ sid
+ doc = build_or_update_doc(sid, session, options)
+ doc.save
+ return sid
+ # if we can't store the session we just return false.
+ rescue CouchRest::Unauthorized,
+ Errno::EHOSTUNREACH,
+ Errno::ECONNREFUSED => e
+ return false
+ end
+
+ def destroy_session(env, sid, options)
+ doc = secure_get(sid)
+ doc.delete
+ generate_sid unless options[:drop]
+ rescue CouchRest::NotFound
+ # already destroyed - we're done.
+ generate_sid unless options[:drop]
+ end
+
+ def fetch_session(sid)
+ return nil unless sid
+ doc = secure_get(sid)
+ doc.to_session unless doc.expired?
+ end
+
+ def build_or_update_doc(sid, session, options)
+ CouchRest::Session::Document.build_or_update(sid, session, options)
+ end
+
+ # prevent access to design docs
+ # this should be prevented on a couch permission level as well.
+ # but better be save than sorry.
+ def secure_get(sid)
+ raise CouchRest::NotFound if /^_design\/(.*)/ =~ sid
+ CouchRest::Session::Document.fetch(sid)
+ end
+
+end
diff --git a/vendor/gems/couchrest_session_store/test/database_method_test.rb b/vendor/gems/couchrest_session_store/test/database_method_test.rb
new file mode 100644
index 0000000..18985c3
--- /dev/null
+++ b/vendor/gems/couchrest_session_store/test/database_method_test.rb
@@ -0,0 +1,116 @@
+require_relative 'test_helper'
+
+class DatabaseMethodTest < MiniTest::Test
+
+ class TestModel < CouchRest::Model::Base
+ include CouchRest::Model::DatabaseMethod
+
+ use_database_method :db_name
+ property :dbname, String
+ property :confirm, String
+
+ def db_name
+ "test_db_#{self[:dbname]}"
+ end
+ end
+
+ def test_instance_method
+ doc1 = TestModel.new({:dbname => 'one'})
+ doc1.database.create!
+ assert doc1.database.root.ends_with?('test_db_one')
+ assert doc1.save
+ doc1.update_attributes(:confirm => 'yep')
+
+ doc2 = TestModel.new({:dbname => 'two'})
+ doc2.database.create!
+ assert doc2.database.root.ends_with?('test_db_two')
+ assert doc2.save
+ doc2.confirm = 'sure'
+ doc2.save!
+
+ doc1_copy = CouchRest.get([doc1.database.root, doc1.id].join('/'))
+ assert_equal "yep", doc1_copy["confirm"]
+
+ doc2_copy = CouchRest.get([doc2.database.root, doc2.id].join('/'))
+ assert_equal "sure", doc2_copy["confirm"]
+
+ doc1.database.delete!
+ doc2.database.delete!
+ end
+
+ def test_switch_db
+ doc_red = TestModel.new({:dbname => 'red', :confirm => 'rose'})
+ doc_red.database.create!
+ root = doc_red.database.root
+
+ doc_blue = doc_red.clone
+ doc_blue.dbname = 'blue'
+ doc_blue.database!
+ doc_blue.save!
+
+ doc_blue_copy = CouchRest.get([root.sub('red','blue'), doc_blue.id].join('/'))
+ assert_equal "rose", doc_blue_copy["confirm"]
+
+ doc_red.database.delete!
+ doc_blue.database.delete!
+ end
+
+ #
+ # A test scenario for database_method in which some user accounts
+ # are stored in a seperate temporary database (so that the test
+ # accounts don't bloat the normal database).
+ #
+
+ class User < CouchRest::Model::Base
+ include CouchRest::Model::DatabaseMethod
+
+ use_database_method :db_name
+ property :login, String
+ before_save :create_db
+
+ class << self
+ def get(id, db = database)
+ result = super(id, db)
+ if result.nil?
+ return super(id, choose_database('test-user'))
+ else
+ return result
+ end
+ end
+ alias :find :get
+ end
+
+ protected
+
+ def self.db_name(login = nil)
+ if !login.nil? && login =~ /test-user/
+ 'tmp_users'
+ else
+ 'users'
+ end
+ end
+
+ def db_name
+ self.class.db_name(self.login)
+ end
+
+ def create_db
+ unless database_exists?(db_name)
+ self.database!
+ end
+ end
+
+ end
+
+ def test_tmp_user_db
+ user1 = User.new({:login => 'test-user-1'})
+ assert user1.save
+ assert User.find(user1.id), 'should find user in tmp_users'
+ assert_equal user1.login, User.find(user1.id).login
+ assert_equal 'test-user-1', User.server.database('couchrest_tmp_users').get(user1.id)['login']
+ assert_raises CouchRest::NotFound do
+ User.server.database('couchrest_users').get(user1.id)
+ end
+ end
+
+end
diff --git a/vendor/gems/couchrest_session_store/test/session_store_test.rb b/vendor/gems/couchrest_session_store/test/session_store_test.rb
new file mode 100644
index 0000000..4fbf30b
--- /dev/null
+++ b/vendor/gems/couchrest_session_store/test/session_store_test.rb
@@ -0,0 +1,168 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+
+class SessionStoreTest < MiniTest::Test
+
+ def test_session_initialization
+ sid, session = store.send :get_session, env, nil
+ assert sid
+ assert_equal Hash.new, session
+ end
+
+ def test_normal_session_flow
+ sid, session = never_expiring_session
+ assert_equal [sid, session], store.send(:get_session, env, sid)
+ store.send :destroy_session, env, sid, {}
+ end
+
+ def test_updating_session
+ sid, session = never_expiring_session
+ session[:bla] = "blub"
+ store.send :set_session, env, sid, session, {}
+ assert_equal [sid, session], store.send(:get_session, env, sid)
+ store.send :destroy_session, env, sid, {}
+ end
+
+ def test_prevent_access_to_design_docs
+ sid = '_design/bla'
+ session = {views: 'my hacked view'}
+ assert_raises CouchRest::NotFound do
+ store_session(sid, session)
+ end
+ end
+
+ def test_unmarshalled_session_flow
+ sid, session = init_session
+ store_session sid, session, :marshal_data => false
+ new_sid, new_session = store.send(:get_session, env, sid)
+ assert_equal sid, new_sid
+ assert_equal session[:key], new_session["key"]
+ store.send :destroy_session, env, sid, {}
+ end
+
+ def test_unmarshalled_data
+ sid, session = init_session
+ store_session sid, session, :marshal_data => false
+ couch = CouchTester.new
+ data = couch.get(sid)["data"]
+ assert_equal session[:key], data["key"]
+ end
+
+ def test_logout_in_between
+ sid, session = never_expiring_session
+ store.send :destroy_session, env, sid, {}
+ other_sid, other_session = store.send(:get_session, env, sid)
+ assert_equal Hash.new, other_session
+ end
+
+ def test_can_logout_twice
+ sid, session = never_expiring_session
+ store.send :destroy_session, env, sid, {}
+ store.send :destroy_session, env, sid, {}
+ other_sid, other_session = store.send(:get_session, env, sid)
+ assert_equal Hash.new, other_session
+ end
+
+ def test_stored_and_not_expired_yet
+ sid, session = expiring_session
+ doc = CouchRest::Session::Document.fetch(sid)
+ expires = doc.send :expires
+ assert expires
+ assert !doc.expired?
+ assert (expires - Time.now) > 0, "Exiry should be in the future"
+ assert (expires - Time.now) <= 300, "Should expire after 300 seconds - not more"
+ assert_equal [sid, session], store.send(:get_session, env, sid)
+ end
+
+ def test_stored_but_expired
+ sid, session = expired_session
+ other_sid, other_session = store.send(:get_session, env, sid)
+ assert_equal Hash.new, other_session, "session should have expired"
+ assert other_sid != sid
+ end
+
+ def test_find_expired_sessions
+ expired, expiring, never_expiring = seed_sessions
+ expired_session_ids = store.expired.map {|row| row['id']}
+ assert expired_session_ids.include?(expired)
+ assert !expired_session_ids.include?(expiring)
+ assert !expired_session_ids.include?(never_expiring)
+ end
+
+ def test_find_never_expiring_sessions
+ expired, expiring, never_expiring = seed_sessions
+ never_expiring_session_ids = store.never_expiring.map {|row| row['id']}
+ assert never_expiring_session_ids.include?(never_expiring)
+ assert !never_expiring_session_ids.include?(expiring)
+ assert !never_expiring_session_ids.include?(expired)
+ end
+
+ def test_cleanup_expired_sessions
+ sid, session = expired_session
+ store.cleanup(store.expired)
+ assert_raises CouchRest::NotFound do
+ CouchTester.new.get(sid)
+ end
+ end
+
+ def test_keep_fresh_during_cleanup
+ sid, session = expiring_session
+ store.cleanup(store.expired)
+ assert_equal [sid, session], store.send(:get_session, env, sid)
+ end
+
+ def test_store_without_expiry
+ sid, session = never_expiring_session
+ couch = CouchTester.new
+ assert_nil couch.get(sid)["expires"]
+ assert_equal [sid, session], store.send(:get_session, env, sid)
+ end
+
+ def app
+ nil
+ end
+
+ def store(options = {})
+ @store ||= CouchRest::Session::Store.new(app, options)
+ end
+
+ def env(settings = {})
+ env ||= settings
+ end
+
+ # returns the session ids of an expired, and expiring and a never
+ # expiring session
+ def seed_sessions
+ [expired_session, expiring_session, never_expiring_session].map(&:first)
+ end
+
+ def never_expiring_session
+ store_session *init_session
+ end
+
+ def expiring_session
+ sid, session = init_session
+ store_session(sid, session, expire_after: 300)
+ end
+
+ def expired_session
+ expire_session *expiring_session
+ end
+
+ def init_session
+ sid, session = store.send :get_session, env, nil
+ session[:key] = "stub"
+ return sid, session
+ end
+
+ def store_session(sid, session, options = {})
+ store.send :set_session, env, sid, session, options
+ return sid, session
+ end
+
+ def expire_session(sid, session)
+ CouchTester.new.update sid,
+ "expires" => (Time.now - 10.minutes).utc.iso8601
+ return sid, session
+ end
+
+end