|
| 1 | +(defpackage :io.github.cl-sdk.wst.rate-limit.redis-store.test |
| 2 | + (:use #:cl)) |
| 3 | + |
| 4 | +(in-package :io.github.cl-sdk.wst.rate-limit.redis-store.test) |
| 5 | + |
| 6 | +(5am:def-suite wst.rate-limit.redis-store.suite |
| 7 | + :description "Tests for the wst.rate-limit.redis-store package.") |
| 8 | + |
| 9 | +(5am:in-suite wst.rate-limit.redis-store.suite) |
| 10 | + |
| 11 | +(defun ensure-rate-limit-entry (records key) |
| 12 | + (or (gethash key records) |
| 13 | + (setf (gethash key records) (make-hash-table :test #'equal)))) |
| 14 | + |
| 15 | +(defmacro with-mocked-rate-limit-redis ((records expiries) &body body) |
| 16 | + `(let* ((hmget-original (symbol-function 'redis:red-hmget)) |
| 17 | + (hmset-original (symbol-function 'redis:red-hmset)) |
| 18 | + (expire-original (symbol-function 'redis:red-expire)) |
| 19 | + (del-original (symbol-function 'redis:red-del))) |
| 20 | + (unwind-protect |
| 21 | + (progn |
| 22 | + (setf (symbol-function 'redis:red-hmget) |
| 23 | + (lambda (key field &rest fields) |
| 24 | + (let* ((entry (gethash key ,records)) |
| 25 | + (wanted-fields (cons field fields))) |
| 26 | + |
| 27 | + (mapcar (lambda (field-name) |
| 28 | + (and entry (gethash field-name entry))) |
| 29 | + wanted-fields))) |
| 30 | + (symbol-function 'redis:red-hmset) |
| 31 | + (lambda (key &rest fields-and-values) |
| 32 | + (let ((entry (ensure-rate-limit-entry ,records key))) |
| 33 | + (loop for (field value) on fields-and-values by #'cddr |
| 34 | + do (setf (gethash field entry) value))) |
| 35 | + "OK") |
| 36 | + (symbol-function 'redis:red-expire) |
| 37 | + (lambda (key ttl) |
| 38 | + (setf (gethash key ,expiries) ttl) |
| 39 | + t) |
| 40 | + (symbol-function 'redis:red-del) |
| 41 | + (lambda (key &rest keys) |
| 42 | + (let ((removed 0)) |
| 43 | + (dolist (k (cons key keys) removed) |
| 44 | + (when (gethash k ,records) |
| 45 | + (incf removed)) |
| 46 | + (remhash k ,records) |
| 47 | + (remhash k ,expiries))))) |
| 48 | + ,@body) |
| 49 | + (setf (symbol-function 'redis:red-hmget) hmget-original |
| 50 | + (symbol-function 'redis:red-hmset) hmset-original |
| 51 | + (symbol-function 'redis:red-expire) expire-original |
| 52 | + (symbol-function 'redis:red-del) del-original)))) |
| 53 | + |
| 54 | +(5am:def-test redis-rate-limit-store-roundtrip () |
| 55 | + (let ((records (make-hash-table :test #'equal)) |
| 56 | + (expiries (make-hash-table :test #'equal))) |
| 57 | + (with-mocked-rate-limit-redis (records expiries) |
| 58 | + (let ((store (make-instance 'io.github.cl-sdk.wst.rate-limit.redis-store:redis-store))) |
| 59 | + (multiple-value-bind (count start) |
| 60 | + (io.github.cl-sdk.wst.rate-limit.store:fetch-window store :client-a) |
| 61 | + (5am:is-false count) |
| 62 | + (5am:is-false start)) |
| 63 | + (io.github.cl-sdk.wst.rate-limit.store:save-window store :client-a 3 1000) |
| 64 | + (multiple-value-bind (count start) |
| 65 | + (io.github.cl-sdk.wst.rate-limit.store:fetch-window store :client-a) |
| 66 | + (5am:is (= 3 count)) |
| 67 | + (5am:is (= 1000 start))) |
| 68 | + (io.github.cl-sdk.wst.rate-limit.store:delete-window store :client-a) |
| 69 | + (multiple-value-bind (count start) |
| 70 | + (io.github.cl-sdk.wst.rate-limit.store:fetch-window store :client-a) |
| 71 | + (5am:is-false count) |
| 72 | + (5am:is-false start)))))) |
| 73 | + |
| 74 | +(5am:def-test redis-rate-limit-store-applies-expiry-when-configured () |
| 75 | + (let ((records (make-hash-table :test #'equal)) |
| 76 | + (expiries (make-hash-table :test #'equal))) |
| 77 | + (with-mocked-rate-limit-redis (records expiries) |
| 78 | + (let ((store (make-instance 'io.github.cl-sdk.wst.rate-limit.redis-store:redis-store |
| 79 | + :window-seconds 42))) |
| 80 | + (io.github.cl-sdk.wst.rate-limit.store:save-window store :client-b 1 2000) |
| 81 | + (5am:is (= 42 (gethash "wst:rate-limit::CLIENT-B" expiries))))))) |
0 commit comments