this repo has no description

⌛ Mock current time in tests

+36 -7
+20 -1
src/glimit/rate_limiter.gleam
··· 22 22 /// Epoch timestamp of the last time the rate limiter was updated. 23 23 /// 24 24 last_update: Option(Int), 25 + /// Timestamp that overrides the current time for testing purposes. 26 + /// 27 + now: Option(Int), 25 28 ) 26 29 } 27 30 28 31 /// Updates the state to reflect the passage of time. 29 32 /// 30 33 fn refill_bucket(state: State) -> State { 31 - let now = utils.now() 34 + let now = case state.now { 35 + None -> utils.now() 36 + Some(now) -> now 37 + } 32 38 let time_diff = case state.last_update { 33 39 None -> 0 34 40 Some(last_update) -> now - last_update ··· 63 69 /// Returns True if the token bucket is full. 64 70 /// 65 71 HasFullBucket(reply_with: Subject(Bool)) 72 + 73 + /// Set the current time for testing purposes. 74 + /// 75 + SetNow(now: Int) 66 76 } 67 77 68 78 fn handle_message(message: Message, state: State) -> actor.Next(Message, State) { ··· 87 97 actor.send(client, result) 88 98 actor.continue(state) 89 99 } 100 + 101 + SetNow(now) -> actor.continue(State(..state, now: Some(now))) 90 102 } 91 103 } 92 104 ··· 102 114 token_rate: token_rate, 103 115 token_count: max_token_count, 104 116 last_update: None, 117 + now: None, 105 118 ) 106 119 actor.start(state, handle_message) 107 120 |> result.nil_error ··· 124 137 pub fn has_full_bucket(rate_limiter: Subject(Message)) -> Bool { 125 138 actor.call(rate_limiter, HasFullBucket, 10) 126 139 } 140 + 141 + /// Set the current time for testing purposes. 142 + /// 143 + pub fn set_now(rate_limiter: Subject(Message), now: Int) -> Nil { 144 + actor.send(rate_limiter, SetNow(now)) 145 + }
+3
src/glimit/registry.gleam
··· 13 13 Subject(Message(id)) 14 14 15 15 /// The rate limiter registry state. 16 + /// 16 17 type State(id) { 17 18 State( 18 19 /// The maximum number of tokens. ··· 79 80 } 80 81 } 81 82 } 83 + 82 84 GetAll(client) -> { 83 85 let rate_limiters = 84 86 state.registry ··· 87 89 actor.send(client, rate_limiters) 88 90 actor.continue(state) 89 91 } 92 + 90 93 Remove(identifier, client) -> { 91 94 let registry = state.registry |> dict.delete(identifier) 92 95 let state = State(..state, registry: registry)
-2
test/glimit_rate_limiter_test.gleam
··· 1 1 import gleeunit/should 2 2 import glimit/rate_limiter 3 3 4 - // TODO: find a way to mock time so we can test the refilling of the rate limiter. 5 - 6 4 pub fn rate_limiter_test() { 7 5 let limiter = case rate_limiter.new(2, 2) { 8 6 Ok(limiter) -> limiter
+13 -4
test/glimit_test.gleam
··· 1 - import gleam/erlang/process 2 1 import gleeunit 3 2 import gleeunit/should 4 3 import glimit 4 + import glimit/rate_limiter 5 + import glimit/registry 5 6 6 7 pub fn main() { 7 8 gleeunit.main() ··· 48 49 pub fn burst_limit_test() { 49 50 let limiter = 50 51 glimit.new() 51 - |> glimit.per_second(2) 52 + |> glimit.per_second(1) 52 53 |> glimit.burst_limit(3) 53 54 |> glimit.identifier(fn(_) { "id" }) 54 55 |> glimit.on_limit_exceeded(fn(_) { "Stop!" }) ··· 58 59 fn(_) { "OK" } 59 60 |> glimit.apply(limiter) 60 61 62 + let assert Ok(rate_limiter) = 63 + limiter.rate_limiter_registry 64 + |> registry.get_or_create("id") 65 + 66 + rate_limiter |> rate_limiter.set_now(0) 61 67 func(Nil) |> should.equal("OK") 62 68 func(Nil) |> should.equal("OK") 63 69 func(Nil) |> should.equal("OK") 64 70 func(Nil) |> should.equal("Stop!") 65 71 func(Nil) |> should.equal("Stop!") 66 72 67 - // TODO: mock time to avoid sleeping 😴 68 - process.sleep(1000) 73 + rate_limiter |> rate_limiter.set_now(1) 74 + func(Nil) |> should.equal("OK") 75 + func(Nil) |> should.equal("Stop!") 76 + func(Nil) |> should.equal("Stop!") 69 77 78 + rate_limiter |> rate_limiter.set_now(3) 70 79 func(Nil) |> should.equal("OK") 71 80 func(Nil) |> should.equal("OK") 72 81 func(Nil) |> should.equal("Stop!")