aging-session/test/aging_session/core_test.clj

238 lines
10 KiB
Clojure

(ns aging-session.core-test
(:require
[clojure.test :refer :all]
[ring.middleware.session.store :refer :all]
[aging-session.core :refer :all]))
(defn ->basic-aging-memory-store
[& [opts]]
(aging-memory-store
30
(merge
{:refresh-on-read true
:refresh-on-write true
:sweep-threshold nil
:sweep-interval 15}
opts)))
(deftest basic-read-empty
(testing "Test session reads when there is no session value for that key."
(let [as (->basic-aging-memory-store)]
(is (nil? (read-session as "mykey"))
"returns nil for non-existent session read"))))
(deftest basic-write
(testing "Test session writes and reads."
(let [as (->basic-aging-memory-store)]
(write-session as "mykey" {:a 1})
(is (= (read-session as "mykey") {:a 1})
"session value was written")
(write-session as "mykey" {:a 2})
(is (= (read-session as "mykey") {:a 2})
"session value was updated"))))
(deftest basic-delete
(testing "Test session delete."
(let [as (->basic-aging-memory-store)]
(write-session as "mykey" {:a 1})
(is (= (read-session as "mykey") {:a 1})
"session value was written")
(delete-session as "mykey")
(is (nil? (read-session as "mykey"))
"session value is no longer present"))))
(deftest timestamp-on-creation
(testing "Test the behaviour where each entry's timestamp is set only on session creation."
(let [as (->basic-aging-memory-store
{:refresh-on-read false
:refresh-on-write false})]
(write-session as "mykey" {:foo 1})
(let [ts1 (read-timestamp as "mykey")]
(is (integer? ts1)
"timestamp was set on session write")
(write-session as "mykey" {:foo 2})
(Thread/sleep 10)
(is (= ts1 (read-timestamp as "mykey"))
"timestamp is unchanged for this session entry after it was updated")
(is (= (read-session as "mykey") {:foo 2})
"updated session entry value was written successfully")))))
(deftest timestamp-on-write-only
(testing "Test the behaviour where each entry's timestamp is refreshed on write (not read)."
(let [as (->basic-aging-memory-store
{:refresh-on-read false
:refresh-on-write true})]
(write-session as "mykey" {:foo 1})
(let [ts1 (read-timestamp as "mykey")]
(is (integer? ts1)
"timestamp was set on session write")
(is (= (read-session as "mykey") {:foo 1})
"session value can be read")
(Thread/sleep 10)
(is (= ts1 (read-timestamp as "mykey"))
"reading the session value did not update its timestamp")
(write-session as "mykey" {:foo 2})
(Thread/sleep 10)
(is (not (= ts1 (read-timestamp as "mykey")))
"timestamp of the session entry was updated after its value was updated")
(is (= (read-session as "mykey") {:foo 2})
"session value was updated successfully")))))
(deftest timestamp-on-read-only
(testing "Test the behaviour where each entry's timestamp is refreshed on read (not write)."
(let [as (->basic-aging-memory-store
{:refresh-on-read true
:refresh-on-write false})]
(write-session as "mykey" {:foo 1})
(let [ts1 (read-timestamp as "mykey")]
(is (integer? ts1)
"timestamp was set on session write")
(Thread/sleep 10)
(is (= (read-session as "mykey") {:foo 1})
"session value can be read")
(let [ts2 (read-timestamp as "mykey")]
(is (not (= ts1 ts2))
"timestamp of the session entry was updated after its value was read")
(is (= (read-session as "mykey") {:foo 1})
"session value can still be read successfully")
(Thread/sleep 10)
(let [ts3 (read-timestamp as "mykey")]
(write-session as "mykey" {:foo 2})
(Thread/sleep 10)
(is (= ts3 (read-timestamp as "mykey"))
"timestamp of the session entry was not updated after its value was written")
(is (= (read-session as "mykey") {:foo 2})
"session value was updated successfully")
(Thread/sleep 10)
(is (not (= ts3 (read-timestamp as "mykey")))
"timestamp of the session entry was updated after its new value was read")))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(deftest individual-session-entries-are-expired-when-read
(testing "Individual session entries are expired appropriately when read, independently of the sweep thread."
(let [as (aging-memory-store
1 ; expire after 1 second
{:sweep-threshold 1 ; sweeper thread write threshold is after every single write
:sweep-interval 10 ; sweeper thread tries to run every 10 seconds
})]
(write-session as "mykey" {:foo 1})
(is (= (read-session as "mykey") {:foo 1})
"session entry was written")
(Thread/sleep 1500) ; little delay, but too short for sweeper thread to have run
(is (nil? (read-session as "mykey"))
"session entry should no longer be present"))))
(deftest sweeper-thread-expires-entries-at-interval-only-when-threshold-reached
(testing "When a threshold is specified, the sweeper thread expires entries whenever it runs only when the operation (write) threshold is reached."
(let [as (aging-memory-store
1 ; expire after 1 second
{:refresh-on-read true
:refresh-on-write true
:sweep-threshold 5 ; only trigger sweep after 5 writes
:sweep-interval 1 ; sweeper thread tries to run every 1 second
})]
(write-session as "mykey" {:foo 1})
(Thread/sleep 2000) ; wait long enough for session ttl to elapse
; key should still exist, even though it's expired (not enough writes have occurred)
(is (integer? (read-timestamp as "mykey"))
"session entry should still be present even though it has expired")
; key should exist for three more writes
(write-session as "other-key" {:foo 1})
(is (integer? (read-timestamp as "mykey"))
"session entry should still be present even though it has expired")
(write-session as "other-key" {:foo 1})
(is (integer? (read-timestamp as "mykey"))
"session entry should still be present even though it has expired")
(write-session as "other-key" {:foo 1})
(is (integer? (read-timestamp as "mykey"))
"session entry should still be present even though it has expired")
; on the fifth write and after 1 second, key should not exist
(write-session as "other-key" {:foo 1})
(Thread/sleep 2000) ; allow time for sweeper thread to run
(is (nil? (read-timestamp as "mykey"))
"session entry should have been removed now"))))
(deftest sweeper-thread-expires-entries-at-interval-with-no-threshold-set
(testing "When a threshold is NOT specified, the sweeper thread expires entries whenever it runs."
(let [as (aging-memory-store
1 ; expire after 1 second
{:refresh-on-read true
:refresh-on-write true
:sweep-threshold nil ; no sweeper thread threshold
:sweep-interval 1 ; sweeper thread tries to run every 1 second
})]
(write-session as "mykey" {:foo 1})
(Thread/sleep 20)
(is (integer? (read-timestamp as "mykey"))
"session entry should still be present")
; do nothing in the mean time (no write operations)
(Thread/sleep 2000) ; wait long enough for session ttl to elapse
(is (nil? (read-timestamp as "mykey"))
"session entry should have been removed now")
; now lets do this again, but read the value (thus, triggering a timestamp refresh) a couple times
(is (nil? (read-session as "mykey"))
"session entry should not be there yet")
(write-session as "mykey" {:foo 1})
(is (= (read-session as "mykey") {:foo 1})
"session entry should now be present")
; repeatedly re-read the session for a period of time over the session store ttl so as to not let
; it expire (because refresh-on-read is enabled)
(doseq [_ (range 10)]
(Thread/sleep 200)
(is (= (read-session as "mykey") {:foo 1})
"session entry should still be present"))
; now wait long enough without reading to let it expire
(Thread/sleep 2000)
(is (nil? (read-session as "mykey"))
"session entry should be gone now")
)))
(deftest refresh-on-read-nonexistent-key-then-sweep
(testing "Test an empty session read (with refresh-on-read enabled) then check that the expiry sweep still works"
(let [as (aging-memory-store
1 ; expire after 1 second
{:refresh-on-read true
:sweep-threshold 1 ; sweep runs after every write
:sweep-interval 1 ; sweep thread tries to run every 1 second
})]
(is (nil? (read-session as "foo"))
"no session entry present for this key")
(Thread/sleep 1500)
; read again to trigger the sweep
(is (nil? (read-session as "foo"))
"still no session entry present for this key"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(deftest sweeper-thread-can-be-stopped
(let [as (->basic-aging-memory-store)]
(Thread/sleep 2000)
(is (.isAlive ^Thread (:thread as))
"sweeper thread is currently alive")
(stop as)
(Thread/sleep 1000)
(is (not (.isAlive ^Thread (:thread as)))
"sweeper thread is no longer alive")))
(deftest can-get-all-sessions
(let [as (->basic-aging-memory-store)]
(write-session as "a" {:foo 1})
(write-session as "b" {:bar 2})
(let [sessions (all-entries as)]
(is (= 2 (count sessions)))
(is (= (get-in sessions ["a" :value]) {:foo 1}))
(is (= (get-in sessions ["b" :value]) {:bar 2})))))
#_(run-tests)