can_check_throttle_status_if_exceeded()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
c 0
b 0
f 0
dl 0
loc 22
rs 9.7
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Zenstruck\Governator\Tests\Integration;
4
5
use PHPUnit\Framework\TestCase;
6
use Zenstruck\Governator\Exception\QuotaExceeded;
7
use Zenstruck\Governator\Store;
8
use Zenstruck\Governator\ThrottleFactory;
9
10
/**
11
 * @author Kevin Bond <[email protected]>
12
 */
13
abstract class BaseThrottleTest extends TestCase
14
{
15
    /**
16
     * @test
17
     */
18
    public function can_acquire_throttle(): void
19
    {
20
        $resource = 'foo';
21
        $limit = 5;
22
        $ttl = 60;
23
        $factory = $this->factory();
24
        $factory->create($resource, $limit, $ttl)->reset();
25
26
        $quota = $factory->create($resource, $limit, $ttl)->acquire();
27
28
        $this->assertSame('foo', $quota->resource());
29
        $this->assertSame(5, $quota->limit());
30
        $this->assertSame(1, $quota->hits());
31
        $this->assertSame(4, $quota->remaining());
32
        $this->assertSame(60, $quota->resetsIn());
33
        $this->assertSame(time() + 60, $quota->resetsAt()->getTimestamp());
34
35
        $quota = $factory->throttle($resource)->allow($limit)->every($ttl)->acquire();
36
37
        $this->assertSame(5, $quota->limit());
38
        $this->assertSame(2, $quota->hits());
39
        $this->assertSame(3, $quota->remaining());
40
        $this->assertSame(60, $quota->resetsIn());
41
42
        sleep(2);
43
44
        $quota = $factory->throttle($resource)->allow($limit)->every($ttl)->acquire();
45
46
        $this->assertSame(5, $quota->limit());
47
        $this->assertSame(3, $quota->hits());
48
        $this->assertSame(2, $quota->remaining());
49
        $this->assertSame(58, $quota->resetsIn());
50
    }
51
52
    /**
53
     * @test
54
     */
55
    public function ensure_resets_after_ttl(): void
56
    {
57
        $resource = 'foo';
58
        $limit = 2;
59
        $ttl = 2;
60
        $factory = $this->factory();
61
        $factory->create($resource, $limit, $ttl)->reset();
62
63
        $quota = $factory->create($resource, $limit, $ttl)->acquire();
64
65
        $this->assertSame(1, $quota->hits());
66
        $this->assertSame(1, $quota->remaining());
67
        $this->assertSame(2, $quota->resetsIn());
68
69
        $quota = $factory->create($resource, $limit, $ttl)->acquire();
70
71
        $this->assertSame(2, $quota->hits());
72
        $this->assertSame(0, $quota->remaining());
73
        $this->assertSame(2, $quota->resetsIn());
74
75
        sleep($quota->resetsIn());
76
77
        $quota = $factory->create($resource, $limit, $ttl)->acquire();
78
        $this->assertSame(1, $quota->hits());
79
        $this->assertSame(1, $quota->remaining());
80
        $this->assertSame(2, $quota->resetsIn());
81
    }
82
83
    /**
84
     * @test
85
     */
86
    public function exceeding_limit_throws_rate_limit_exceeded_exception(): void
87
    {
88
        $resource = 'foo';
89
        $limit = 5;
90
        $ttl = 2;
91
        $factory = $this->factory();
92
        $factory->create($resource, $limit, $ttl)->reset();
93
94
        $factory->create($resource, $limit, $ttl)->acquire();
95
        $factory->create($resource, $limit, $ttl)->acquire();
96
        $factory->create($resource, $limit, $ttl)->acquire();
97
        $factory->create($resource, $limit, $ttl)->acquire();
98
        $factory->create($resource, $limit, $ttl)->acquire();
99
100
        try {
101
            $factory->create($resource, $limit, $ttl)->acquire();
102
        } catch (QuotaExceeded $exception) {
103
            $this->assertSame('Quota Exceeded (6/5), resets in 2 seconds.', $exception->getMessage());
104
            $this->assertSame('foo', $exception->resource());
105
            $this->assertSame(5, $exception->limit());
106
            $this->assertSame(6, $exception->hits());
107
            $this->assertSame(0, $exception->remaining());
108
            $this->assertSame(2, $exception->resetsIn());
109
            $this->assertSame(time() + 2, $exception->resetsAt()->getTimestamp());
110
            $this->assertSame('foo', $exception->quota()->resource());
111
            $this->assertSame(5, $exception->quota()->limit());
112
            $this->assertSame(6, $exception->quota()->hits());
113
            $this->assertSame(0, $exception->quota()->remaining());
114
            $this->assertSame(2, $exception->quota()->resetsIn());
115
            $this->assertSame(time() + 2, $exception->quota()->resetsAt()->getTimestamp());
116
117
            sleep($exception->resetsIn());
118
119
            $quota = $factory->create($resource, $limit, $ttl)->acquire();
120
121
            $this->assertSame(5, $quota->limit());
122
            $this->assertSame(1, $quota->hits());
123
            $this->assertSame(4, $quota->remaining());
124
            $this->assertSame(2, $quota->resetsIn());
125
126
            return;
127
        }
128
129
        $this->fail('Exception not thrown.');
130
    }
131
132
    /**
133
     * @test
134
     */
135
    public function acquire_with_block_returns_quota_right_away_if_not_exceeded(): void
136
    {
137
        $resource = 'foo';
138
        $limit = 2;
139
        $ttl = 2;
140
        $factory = $this->factory();
141
        $factory->create($resource, $limit, $ttl)->reset();
142
143
        $start = time();
144
145
        $quota = $factory->create($resource, $limit, $ttl)->acquire(10);
146
147
        $this->assertSame($start, time());
148
        $this->assertSame(1, $quota->hits());
149
        $this->assertSame(1, $quota->remaining());
150
        $this->assertSame(2, $quota->resetsIn());
151
    }
152
153
    /**
154
     * @test
155
     */
156
    public function can_block_throttle_if_available_within_passed_time(): void
157
    {
158
        $resource = 'foo';
159
        $limit = 2;
160
        $ttl = 2;
161
        $factory = $this->factory();
162
        $factory->create($resource, $limit, $ttl)->reset();
163
164
        $start = time();
165
        $factory->create($resource, $limit, $ttl)->acquire();
166
        $factory->create($resource, $limit, $ttl)->acquire();
167
168
        $quota = $factory->throttle($resource)->allow($limit)->every($ttl)->acquire(10);
169
170
        $this->assertSame($start + 2, time());
171
        $this->assertSame(1, $quota->hits());
172
        $this->assertSame(1, $quota->remaining());
173
        $this->assertSame(2, $quota->resetsIn());
174
    }
175
176
    /**
177
     * @test
178
     */
179
    public function can_block_throttle_if_available_at_exactly_passed_time(): void
180
    {
181
        $resource = 'foo';
182
        $limit = 2;
183
        $ttl = 2;
184
        $factory = $this->factory();
185
        $factory->create($resource, $limit, $ttl)->reset();
186
187
        $start = time();
188
        $factory->create($resource, $limit, $ttl)->acquire();
189
        $factory->create($resource, $limit, $ttl)->acquire();
190
191
        $quota = $factory->throttle($resource)->allow($limit)->every($ttl)->acquire(2);
192
193
        $this->assertSame($start + 2, time());
194
        $this->assertSame(1, $quota->hits());
195
        $this->assertSame(1, $quota->remaining());
196
        $this->assertSame(2, $quota->resetsIn());
197
    }
198
199
    /**
200
     * @test
201
     */
202
    public function acquire_with_block_throws_quota_exceeded_exception_right_away_if_not_going_to_be_available_within_passed_time(): void
203
    {
204
        $resource = 'foo';
205
        $limit = 2;
206
        $ttl = 10;
207
        $factory = $this->factory();
208
        $factory->create($resource, $limit, $ttl)->reset();
209
210
        $start = time();
211
        $factory->create($resource, $limit, $ttl)->acquire();
212
        $factory->create($resource, $limit, $ttl)->acquire();
213
214
        try {
215
            $factory->create($resource, $limit, $ttl)->acquire(2);
216
        } catch (QuotaExceeded $exception) {
217
            $this->assertSame($start, time());
218
            $this->assertSame(3, $exception->hits());
219
            $this->assertSame(0, $exception->remaining());
220
            $this->assertSame(10, $exception->resetsIn());
221
222
            return;
223
        }
224
225
        $this->fail('Exception not thrown.');
226
    }
227
228
    /**
229
     * @test
230
     */
231
    public function can_reset_throttle(): void
232
    {
233
        $factory = $this->factory();
234
        $throttle = $factory->create('foo', 5, 60);
235
        $throttle->reset();
236
237
        $this->assertSame(4, $throttle->acquire()->remaining());
238
        $this->assertSame(3, $throttle->acquire()->remaining());
239
240
        $throttle->reset();
241
242
        $this->assertSame(4, $throttle->acquire()->remaining());
243
        $this->assertSame(3, $throttle->acquire()->remaining());
244
245
        $factory->throttle('foo')->allow(5)->every(60)->reset();
246
247
        $this->assertSame(4, $throttle->acquire()->remaining());
248
        $this->assertSame(3, $throttle->acquire()->remaining());
249
    }
250
251
    /**
252
     * @test
253
     */
254
    public function can_get_status_of_throttle_that_has_not_been_hit(): void
255
    {
256
        $resource = 'foo';
257
        $limit = 5;
258
        $ttl = 60;
259
        $throttle = $this->factory()->create($resource, $limit, $ttl);
260
        $throttle->reset();
261
262
        $quota = $throttle->status();
263
264
        $this->assertSame(5, $quota->limit());
265
        $this->assertSame(0, $quota->hits());
266
        $this->assertSame(5, $quota->remaining());
267
        $this->assertSame(60, $quota->resetsIn());
268
    }
269
270
    /**
271
     * @test
272
     */
273
    public function can_get_status_of_throttle_that_has_been_hit(): void
274
    {
275
        $resource = 'foo';
276
        $limit = 5;
277
        $ttl = 60;
278
        $throttle = $this->factory()->create($resource, $limit, $ttl);
279
        $throttle->reset();
280
281
        $throttle->acquire();
282
283
        $quota = $throttle->status();
284
285
        $this->assertSame(5, $quota->limit());
286
        $this->assertSame(1, $quota->hits());
287
        $this->assertSame(4, $quota->remaining());
288
        $this->assertSame(60, $quota->resetsIn());
289
290
        sleep(2);
291
292
        $quota = $throttle->status();
293
294
        $this->assertSame(5, $quota->limit());
295
        $this->assertSame(1, $quota->hits());
296
        $this->assertSame(4, $quota->remaining());
297
        $this->assertSame(58, $quota->resetsIn());
298
299
        $throttle->acquire();
300
301
        $quota = $throttle->status();
302
303
        $this->assertSame(5, $quota->limit());
304
        $this->assertSame(2, $quota->hits());
305
        $this->assertSame(3, $quota->remaining());
306
        $this->assertSame(58, $quota->resetsIn());
307
    }
308
309
    /**
310
     * @test
311
     */
312
    public function can_check_throttle_status_if_exceeded(): void
313
    {
314
        $resource = 'foo';
315
        $limit = 5;
316
        $ttl = 60;
317
        $throttle = $this->factory()->create($resource, $limit, $ttl);
318
        $throttle->reset();
319
320
        $throttle->hit();
321
        $throttle->hit();
322
        $throttle->hit();
323
        $throttle->hit();
324
        $throttle->hit();
325
        $throttle->hit();
326
327
        $quota = $throttle->status();
328
329
        $this->assertSame(5, $quota->limit());
330
        $this->assertSame(6, $quota->hits());
331
        $this->assertSame(0, $quota->remaining());
332
        $this->assertSame(60, $quota->resetsIn());
333
        $this->assertTrue($quota->hasBeenExceeded());
334
    }
335
336
    abstract protected function createStore(): Store;
337
338
    private function factory(): ThrottleFactory
339
    {
340
        return new ThrottleFactory($this->createStore());
341
    }
342
}
343