@withjoy/limiter 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -11
- package/limiter.js +65 -27
- package/package.json +4 -6
- package/tests/limiter.test.js +237 -179
- package/tests/performTestWithTestPerMinute.js +31 -0
- package/tests/sanityCheck.js +12 -7
- package/tests/limitd.config +0 -14
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
### A wrapper for limitd
|
|
1
|
+
### A wrapper for limitd-redis
|
|
2
2
|
|
|
3
3
|
##### Why ?
|
|
4
4
|
|
|
@@ -7,16 +7,10 @@ Provides utilities and rollback functionality for our clients.
|
|
|
7
7
|
### Testing:
|
|
8
8
|
|
|
9
9
|
`npm run test` will run offline tests using a mock.
|
|
10
|
-
`npm run sanity-check` will run against a limitd service configured to run with the
|
|
10
|
+
`npm run sanity-check` will run against a limitd service configured to run with the test buckets.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
check [the Wiki](https://withjoy.atlassian.net/wiki/spaces/KNOW/pages/1905557728/Running+withjoy+limitd+locally)
|
|
15
|
-
|
|
16
|
-
run this in the root of this repo:
|
|
12
|
+
## Quick Test
|
|
17
13
|
```bash
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type=bind,source="$(pwd)"/tests/limitd.config,target=/etc/limitd.config \
|
|
21
|
-
-it joylife/limitd:release-6-d5e4805
|
|
14
|
+
node tests/performTestWithTestPerMinute.js
|
|
15
|
+
|
|
22
16
|
```
|
package/limiter.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
var LimitdRedis = require("limitd-redis"); // name to redis matter
|
|
4
|
+
|
|
5
|
+
const defaultBuckets = {
|
|
6
|
+
emailsByEventId: { size: 3000 },
|
|
7
|
+
emailsByEventIdRate: { size: 500, per_hour: 500 },
|
|
8
|
+
emailsByEventId_Warn: { size: 1000 },
|
|
9
|
+
emailsByEventIdRate_Warn: { size: 200, per_hour: 200 },
|
|
10
|
+
eventsByUserId: { size: 5, per_hour: 5 },
|
|
11
|
+
eventsByIpRate: { size: 240, per_hour: 240 },
|
|
12
|
+
paymentAttempts: { size: 9000 },
|
|
13
|
+
paymentAttemptsByReceiverId: { size: 900 },
|
|
14
|
+
// for development testing
|
|
15
|
+
testPerMinute: { size: 3, per_minute: 1 },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const defaultRedisKeyPrefix = "limitdRedis:";
|
|
5
19
|
|
|
6
20
|
class Operation {
|
|
7
|
-
constructor({opname,type,id,amount}){
|
|
21
|
+
constructor({ opname, type, id, amount }) {
|
|
8
22
|
this.opname = opname;
|
|
9
23
|
this.type = type;
|
|
10
24
|
this.id = id;
|
|
@@ -24,12 +38,11 @@ class Operation {
|
|
|
24
38
|
let connection = null;
|
|
25
39
|
|
|
26
40
|
class Limiter {
|
|
27
|
-
|
|
28
41
|
constructor(config) {
|
|
29
42
|
this._console = config.console;
|
|
43
|
+
this._config = config;
|
|
30
44
|
this._operationList = [];
|
|
31
|
-
this.
|
|
32
|
-
this.connect(config.limitdUrl);
|
|
45
|
+
this._limitdRedis = this.connect();
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
/*
|
|
@@ -37,11 +50,21 @@ class Limiter {
|
|
|
37
50
|
and we invoke this method from the Constructor.
|
|
38
51
|
stub it to prevent a real connect from happening under Test Suite Conditions
|
|
39
52
|
*/
|
|
40
|
-
connect(
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
connect() {
|
|
54
|
+
let buckets = this._config.buckets || {};
|
|
55
|
+
buckets = {
|
|
56
|
+
...defaultBuckets,
|
|
57
|
+
...buckets,
|
|
58
|
+
};
|
|
59
|
+
const keyPrefix = this._config.keyPrefix || defaultRedisKeyPrefix;
|
|
60
|
+
if (!connection) {
|
|
61
|
+
connection = new LimitdRedis({
|
|
62
|
+
uri: this._config.limitdUrl,
|
|
63
|
+
buckets,
|
|
64
|
+
keyPrefix,
|
|
65
|
+
});
|
|
43
66
|
}
|
|
44
|
-
this.
|
|
67
|
+
this._limitdRedis = connection;
|
|
45
68
|
return connection;
|
|
46
69
|
}
|
|
47
70
|
|
|
@@ -49,16 +72,16 @@ class Limiter {
|
|
|
49
72
|
Go through the log of things we did and do the opposite
|
|
50
73
|
*/
|
|
51
74
|
revert() {
|
|
52
|
-
let ops = this._operationList.map(op => op.clone());
|
|
75
|
+
let ops = this._operationList.map((op) => op.clone());
|
|
53
76
|
this._operationList = [];
|
|
54
77
|
|
|
55
78
|
let p = Promise.resolve();
|
|
56
79
|
for (let op of ops) {
|
|
57
80
|
switch (op.opname) {
|
|
58
|
-
case
|
|
81
|
+
case "put":
|
|
59
82
|
p = p.then(() => this._take(op.type, op.id, op.amount));
|
|
60
83
|
break;
|
|
61
|
-
case
|
|
84
|
+
case "take":
|
|
62
85
|
p = p.then(() => this._put(op.type, op.id, op.amount));
|
|
63
86
|
break;
|
|
64
87
|
}
|
|
@@ -71,24 +94,36 @@ class Limiter {
|
|
|
71
94
|
this._operationList.push(op);
|
|
72
95
|
}
|
|
73
96
|
|
|
97
|
+
close() {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
this._limitdRedis.close((err, resp) => {
|
|
100
|
+
if (err) {
|
|
101
|
+
this._console.error(err);
|
|
102
|
+
return reject(err);
|
|
103
|
+
}
|
|
104
|
+
console.log("Connection closed");
|
|
105
|
+
resolve(resp);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
74
109
|
// promise wrapper for limitd lib put
|
|
75
|
-
_put
|
|
110
|
+
_put(type, id, amount) {
|
|
76
111
|
return new Promise((resolve, reject) =>
|
|
77
|
-
this.
|
|
112
|
+
this._limitdRedis.put(type, id, amount, (err, resp) => {
|
|
78
113
|
if (err) {
|
|
79
114
|
this._console.error(err);
|
|
80
115
|
return reject(err);
|
|
81
116
|
}
|
|
82
|
-
this.record(new Operation({opname: "put",type,id,amount}));
|
|
83
|
-
resolve(
|
|
117
|
+
this.record(new Operation({ opname: "put", type, id, amount }));
|
|
118
|
+
resolve(resp);
|
|
84
119
|
})
|
|
85
120
|
);
|
|
86
121
|
}
|
|
87
122
|
|
|
88
123
|
// promise wrapper for limitd lib take
|
|
89
|
-
_take
|
|
124
|
+
_take(type, id, amount) {
|
|
90
125
|
return new Promise((resolve, reject) =>
|
|
91
|
-
this.
|
|
126
|
+
this._limitdRedis.take(type, id, amount, (err, resp) => {
|
|
92
127
|
if (err) {
|
|
93
128
|
this._console.error(err);
|
|
94
129
|
return reject(err);
|
|
@@ -99,10 +134,10 @@ class Limiter {
|
|
|
99
134
|
return reject(new Error("Non Conformant"));
|
|
100
135
|
}
|
|
101
136
|
|
|
102
|
-
this.record(new Operation({opname: "take",type,id,amount}));
|
|
103
|
-
return resolve(
|
|
137
|
+
this.record(new Operation({ opname: "take", type, id, amount }));
|
|
138
|
+
return resolve(resp);
|
|
104
139
|
})
|
|
105
|
-
)
|
|
140
|
+
);
|
|
106
141
|
}
|
|
107
142
|
|
|
108
143
|
transfer(type, id, amount) {
|
|
@@ -112,17 +147,20 @@ class Limiter {
|
|
|
112
147
|
|
|
113
148
|
this._console.info(`Transfering ${amount} from ${type}-${id}`);
|
|
114
149
|
// If amount == 0, there is nothing to do
|
|
115
|
-
if (amount == 0) {
|
|
116
|
-
|
|
150
|
+
if (amount == 0) {
|
|
151
|
+
return Promise.resolve(null);
|
|
152
|
+
}
|
|
153
|
+
if (amount < 0) {
|
|
154
|
+
return this._put(type, id, -amount);
|
|
155
|
+
}
|
|
117
156
|
return this._take(type, id, amount);
|
|
118
157
|
}
|
|
119
158
|
|
|
120
159
|
transferAll(arr) {
|
|
121
160
|
return Promise.all(
|
|
122
|
-
arr.map(spec => this.transfer(spec[0], spec[1], spec[2]))
|
|
123
|
-
)
|
|
161
|
+
arr.map((spec) => this.transfer(spec[0], spec[1], spec[2]))
|
|
162
|
+
);
|
|
124
163
|
}
|
|
125
164
|
}
|
|
126
165
|
|
|
127
|
-
|
|
128
166
|
module.exports = Limiter;
|
package/package.json
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withjoy/limiter",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Api Rate limiter",
|
|
5
5
|
"main": "limiter.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "jest",
|
|
7
|
+
"test": "NODE_ENV=test node --expose-gc --max-old-space-size=6144 node_modules/.bin/jest --logHeapUsage -i --forceExit",
|
|
8
8
|
"sanity-check": "node tests/sanityCheck.js k 10"
|
|
9
9
|
},
|
|
10
10
|
"author": "services@withjoy.com",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"limitd-
|
|
13
|
+
"limitd-redis": "^5.1.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"jest": "^24.9.0"
|
|
17
|
-
"limitd": "^5.3.0",
|
|
18
|
-
"limitdctl": "^1.1.1"
|
|
16
|
+
"jest": "^24.9.0"
|
|
19
17
|
},
|
|
20
18
|
"directories": {
|
|
21
19
|
"test": "tests"
|
package/tests/limiter.test.js
CHANGED
|
@@ -1,212 +1,270 @@
|
|
|
1
|
-
const Limiter = require(
|
|
1
|
+
const Limiter = require("../limiter");
|
|
2
2
|
|
|
3
3
|
const PUT_FAILED = "Failed to put!";
|
|
4
4
|
const TAKE_FAILED = "Failed to take!";
|
|
5
|
+
|
|
6
|
+
const TEST_BUCKETS = {
|
|
7
|
+
emails: {
|
|
8
|
+
size: 10,
|
|
9
|
+
},
|
|
10
|
+
emailRate: {
|
|
11
|
+
size: 10,
|
|
12
|
+
per_hour: 10,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
5
15
|
/*
|
|
6
16
|
The limitd service is essentially a token bank
|
|
7
17
|
that we can withdraw from (take) or deposit to (put)
|
|
8
18
|
*/
|
|
9
19
|
const limiterWithMockService = (succeeds = true, conforms = true) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
20
|
+
let token_pile = 0;
|
|
21
|
+
const limiter = new Limiter({
|
|
22
|
+
console: console,
|
|
23
|
+
limitdUrl: "localhost:6379",
|
|
24
|
+
buckets: TEST_BUCKETS,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
limiter._limitdRedis = new (class MockLimitd {
|
|
28
|
+
constructor(should_succeed, should_conform) {
|
|
29
|
+
this.should_succeed = should_succeed;
|
|
30
|
+
this.should_conform = should_conform;
|
|
31
|
+
}
|
|
32
|
+
put(type, id, amount, callback) {
|
|
33
|
+
console.log("called put", type, id, amount);
|
|
34
|
+
console.log(this.should_succeed);
|
|
35
|
+
if (!this.should_succeed) {
|
|
36
|
+
return callback(new Error(PUT_FAILED), "This is not falsey");
|
|
37
|
+
}
|
|
38
|
+
token_pile += amount;
|
|
39
|
+
callback(null, {});
|
|
40
|
+
}
|
|
41
|
+
take(type, id, amount, callback) {
|
|
42
|
+
console.log("called take", type, id, amount);
|
|
43
|
+
if (!this.should_succeed) {
|
|
44
|
+
return callback(new Error(TAKE_FAILED), null);
|
|
45
|
+
} else if (!this.should_conform) {
|
|
46
|
+
return callback(null, {}); // non coformant
|
|
47
|
+
}
|
|
48
|
+
token_pile -= amount;
|
|
49
|
+
callback(null, { conformant: true });
|
|
50
|
+
}
|
|
51
|
+
_num_tokens() {
|
|
52
|
+
return token_pile;
|
|
53
|
+
}
|
|
54
|
+
})(succeeds, conforms);
|
|
55
|
+
return limiter;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
test("perfrom limitd-redis testing with test bucket", async (done) => {
|
|
59
|
+
const limiter = new Limiter({
|
|
60
|
+
limitdUrl: "localhost:6379",
|
|
61
|
+
console: console,
|
|
62
|
+
buckets: {
|
|
63
|
+
test: { size: 3, per_second: 2 },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
// take 2 # Conformant - Remaining: 1
|
|
67
|
+
const result1 = await limiter.transfer("test", "test-key", 2);
|
|
68
|
+
expect(result1).toMatchObject({
|
|
69
|
+
conformant: true,
|
|
70
|
+
remaining: 1,
|
|
71
|
+
limit: 3,
|
|
72
|
+
delayed: false,
|
|
73
|
+
});
|
|
74
|
+
await expect(limiter.transfer("test", "test-key", 2)).rejects.toThrow(
|
|
75
|
+
"Non Conformant"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Put 1
|
|
79
|
+
const result3 = await limiter.transfer("test", "test-key", -1);
|
|
80
|
+
expect(result3).toMatchObject({
|
|
81
|
+
remaining: 2,
|
|
82
|
+
limit: 3,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
//Now Conformant - Remaining: 2
|
|
86
|
+
const result4 = await limiter.transfer("test", "test-key", 2);
|
|
87
|
+
expect(result4).toMatchObject({
|
|
88
|
+
conformant: true,
|
|
89
|
+
remaining: 0,
|
|
90
|
+
limit: 3,
|
|
91
|
+
delayed: false,
|
|
92
|
+
});
|
|
93
|
+
await limiter.close();
|
|
94
|
+
done();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("Resolves to null if we aren't doing a real transfer", (done) => {
|
|
98
|
+
let limiter = limiterWithMockService();
|
|
99
|
+
limiter.transfer("a", "b", 0, null).then((result) => {
|
|
100
|
+
expect(result).toBe(null);
|
|
101
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
102
|
+
done();
|
|
103
|
+
});
|
|
50
104
|
});
|
|
51
105
|
|
|
52
|
-
test("Adds tokens if provided a negative token amount", done => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
106
|
+
test("Adds tokens if provided a negative token amount", (done) => {
|
|
107
|
+
let limiter = limiterWithMockService();
|
|
108
|
+
limiter.transfer("a", "b", -5).then((result) => {
|
|
109
|
+
expect(result).toMatchObject({});
|
|
110
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(5);
|
|
111
|
+
done();
|
|
112
|
+
});
|
|
60
113
|
});
|
|
61
114
|
|
|
62
|
-
test("Removes tokens if provided a positive token amount", done => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
115
|
+
test("Removes tokens if provided a positive token amount", (done) => {
|
|
116
|
+
let limiter = limiterWithMockService();
|
|
117
|
+
limiter.transfer("a", "b", 5).then((result) => {
|
|
118
|
+
expect(result).toMatchObject({ conformant: true });
|
|
119
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(-5);
|
|
120
|
+
done();
|
|
121
|
+
});
|
|
70
122
|
});
|
|
71
123
|
|
|
72
|
-
test("Reverts all taken tokens when revert is called", done => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
124
|
+
test("Reverts all taken tokens when revert is called", (done) => {
|
|
125
|
+
let limiter = limiterWithMockService();
|
|
126
|
+
limiter
|
|
127
|
+
.transfer("a", "b", 5)
|
|
128
|
+
.then((result) => {
|
|
129
|
+
expect(result).toMatchObject({ conformant: true });
|
|
130
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(-5);
|
|
131
|
+
})
|
|
132
|
+
.then(() => limiter.revert())
|
|
133
|
+
.then(() => {
|
|
134
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
135
|
+
done();
|
|
136
|
+
});
|
|
84
137
|
});
|
|
85
138
|
|
|
86
|
-
test("Reverts all given tokens when revert is called", done => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
test("Reverts all given tokens when revert is called", (done) => {
|
|
140
|
+
let limiter = limiterWithMockService();
|
|
141
|
+
limiter
|
|
142
|
+
.transfer("a", "b", -5)
|
|
143
|
+
.then((result) => {
|
|
144
|
+
expect(result).toMatchObject({});
|
|
145
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(5);
|
|
146
|
+
})
|
|
147
|
+
.then(() => limiter.revert())
|
|
148
|
+
.then(() => {
|
|
149
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
150
|
+
done();
|
|
151
|
+
});
|
|
98
152
|
});
|
|
99
153
|
|
|
100
|
-
test("Reverts a series of actions when revert is called", done => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
154
|
+
test("Reverts a series of actions when revert is called", (done) => {
|
|
155
|
+
let limiter = limiterWithMockService();
|
|
156
|
+
limiter
|
|
157
|
+
.transfer("a", "b", -5)
|
|
158
|
+
.then((result) => {
|
|
159
|
+
expect(result).toMatchObject({});
|
|
160
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(5);
|
|
161
|
+
})
|
|
162
|
+
.then(() => limiter.transfer("a", "b", 2))
|
|
163
|
+
.then((result) => {
|
|
164
|
+
expect(result).toMatchObject({ conformant: true });
|
|
165
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(3);
|
|
166
|
+
})
|
|
167
|
+
.then(() => limiter.revert())
|
|
168
|
+
.then(() => {
|
|
169
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
170
|
+
done();
|
|
171
|
+
});
|
|
117
172
|
});
|
|
118
173
|
|
|
119
|
-
test("Properly executes a transferAll()", done => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
174
|
+
test("Properly executes a transferAll()", (done) => {
|
|
175
|
+
let limiter = limiterWithMockService();
|
|
176
|
+
limiter
|
|
177
|
+
.transferAll([
|
|
178
|
+
["a", "b", -5],
|
|
179
|
+
["a", "b", 2],
|
|
180
|
+
])
|
|
181
|
+
.then((result) => {
|
|
182
|
+
expect(result).toMatchObject([{}, { conformant: true }]);
|
|
183
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(3);
|
|
184
|
+
})
|
|
185
|
+
.then(() => limiter.revert())
|
|
186
|
+
.then(() => {
|
|
187
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
188
|
+
done();
|
|
189
|
+
});
|
|
134
190
|
});
|
|
135
191
|
|
|
136
192
|
test("Errors from the underlying service are propagated (put)", () => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
193
|
+
let limiter = limiterWithMockService((succeeds = false));
|
|
194
|
+
let endsInFailure = limiter.transfer("a", "b", -5);
|
|
195
|
+
return expect(endsInFailure).rejects.toThrow(PUT_FAILED);
|
|
140
196
|
});
|
|
141
197
|
|
|
142
198
|
test("Errors from the underlying service are propagated (take)", () => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
199
|
+
let limiter = limiterWithMockService((succeeds = false));
|
|
200
|
+
let endsInFailure = limiter.transfer("a", "b", 5);
|
|
201
|
+
return expect(endsInFailure).rejects.toThrow(TAKE_FAILED);
|
|
146
202
|
});
|
|
147
203
|
|
|
148
204
|
test("Non-conforming response results in an error", () => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
205
|
+
let limiter = limiterWithMockService((succeeds = true), (conforms = false));
|
|
206
|
+
let endsInFailure = limiter.transfer("a", "b", 5);
|
|
207
|
+
return expect(endsInFailure).rejects.toThrow("Non Conformant");
|
|
152
208
|
});
|
|
153
209
|
|
|
154
|
-
test("An operation that fails is not logged", () => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
210
|
+
test("An operation that fails is not logged", (done) => {
|
|
211
|
+
let limiter = limiterWithMockService();
|
|
212
|
+
limiter
|
|
213
|
+
.transfer("emailRate", "b", -5)
|
|
214
|
+
.then((result) => {
|
|
215
|
+
expect(result).toBe(null);
|
|
216
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(5);
|
|
217
|
+
limiter.succeeds = false; // somebody is playing games
|
|
218
|
+
})
|
|
219
|
+
.then(() => limiter.transfer("emailRate", "b", 2))
|
|
220
|
+
.catch(() => "failed uniquely!") // do nothing!
|
|
221
|
+
.then((result) => {
|
|
222
|
+
expect(result).toBe("failed uniquely!");
|
|
223
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(5);
|
|
224
|
+
limiter.succeeds = true; // for real this time
|
|
225
|
+
})
|
|
226
|
+
.then(() => limiter.revert())
|
|
227
|
+
.then(() => {
|
|
228
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
229
|
+
done();
|
|
230
|
+
});
|
|
174
231
|
});
|
|
175
232
|
|
|
176
|
-
test("Properly handles spamming revert() calls", done => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
});
|
|
233
|
+
test("Properly handles spamming revert() calls", (done) => {
|
|
234
|
+
let limiter = limiterWithMockService();
|
|
235
|
+
limiter
|
|
236
|
+
.transfer("a", "b", -5)
|
|
237
|
+
.then((result) => {
|
|
238
|
+
expect(result).toMatchObject({});
|
|
239
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(5);
|
|
240
|
+
})
|
|
241
|
+
.then(() => limiter.transfer("a", "b", 2))
|
|
242
|
+
.then((result) => {
|
|
243
|
+
expect(result).toMatchObject({ conformant: true });
|
|
244
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(3);
|
|
245
|
+
})
|
|
246
|
+
.then(() =>
|
|
247
|
+
Promise.all([
|
|
248
|
+
limiter.revert(),
|
|
249
|
+
limiter.revert(),
|
|
250
|
+
limiter.revert(),
|
|
251
|
+
limiter.revert(),
|
|
252
|
+
limiter.revert(),
|
|
253
|
+
limiter.revert(),
|
|
254
|
+
limiter.revert(),
|
|
255
|
+
limiter.revert(),
|
|
256
|
+
limiter.revert(),
|
|
257
|
+
limiter.revert(),
|
|
258
|
+
limiter.revert(),
|
|
259
|
+
limiter.revert(),
|
|
260
|
+
limiter.revert(),
|
|
261
|
+
limiter.revert(),
|
|
262
|
+
limiter.revert(),
|
|
263
|
+
limiter.revert(),
|
|
264
|
+
])
|
|
265
|
+
)
|
|
266
|
+
.then(() => {
|
|
267
|
+
expect(limiter._limitdRedis._num_tokens()).toBe(0);
|
|
268
|
+
done();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var Limiter = require("../limiter");
|
|
2
|
+
|
|
3
|
+
async function performTestWithTestPerMinute() {
|
|
4
|
+
console.log("Testing limitd-redius with bucket testPerMinute");
|
|
5
|
+
const limiter = new Limiter({
|
|
6
|
+
limitdUrl: "localhost:6379",
|
|
7
|
+
console: console,
|
|
8
|
+
});
|
|
9
|
+
// take 2 # Conformant - Remaining: 1
|
|
10
|
+
const result1 = await limiter.transfer("testPerMinute", "key", 2);
|
|
11
|
+
console.log("First Turn Take Result", result1);
|
|
12
|
+
|
|
13
|
+
//Now Conformant - Remaining: 1
|
|
14
|
+
const result2 = await limiter
|
|
15
|
+
.transfer("testPerMinute", "key", 2)
|
|
16
|
+
.catch((err) => {
|
|
17
|
+
console.log("Second Turn Take Error", err.message);
|
|
18
|
+
});
|
|
19
|
+
console.log("Second Turn Take Result", result2);
|
|
20
|
+
|
|
21
|
+
// Put 1
|
|
22
|
+
const result3 = await limiter.transfer("testPerMinute", "key", -1);
|
|
23
|
+
console.log("Third Turn PUT Result", result3);
|
|
24
|
+
|
|
25
|
+
//Now Conformant - Remaining: 2
|
|
26
|
+
const result4 = await limiter.transfer("testPerMinute", "key", 2);
|
|
27
|
+
console.log("Fourth Turn Take Result", result4);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
performTestWithTestPerMinute();
|
package/tests/sanityCheck.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
var Limiter = require("../limiter");
|
|
5
4
|
|
|
5
|
+
const buckets = {
|
|
6
|
+
emails: { size: 10 },
|
|
7
|
+
emailRate: { size: 10, per_hour: 10 },
|
|
8
|
+
};
|
|
9
|
+
|
|
6
10
|
var limiter = new Limiter({
|
|
7
|
-
limitdUrl: "
|
|
8
|
-
console: console
|
|
9
|
-
|
|
11
|
+
limitdUrl: "localhost:6379",
|
|
12
|
+
console: console,
|
|
13
|
+
buckets,
|
|
14
|
+
});
|
|
10
15
|
|
|
11
16
|
var callback = function (err) {
|
|
12
17
|
if (err) {
|
|
@@ -18,10 +23,10 @@ var callback = function (err) {
|
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
var withdrawls = [
|
|
26
|
+
["emails", process.argv[2], -parseInt(process.argv[3])],
|
|
27
|
+
["emailRate", process.argv[2], -parseInt(process.argv[3])],
|
|
21
28
|
["emails", process.argv[2], parseInt(process.argv[3])],
|
|
22
|
-
["emailRate", process.argv[2], parseInt(process.argv[3])]
|
|
29
|
+
["emailRate", process.argv[2], parseInt(process.argv[3])],
|
|
23
30
|
];
|
|
24
31
|
|
|
25
|
-
var withdrawlsComplete = [];
|
|
26
32
|
limiter.transferAll(withdrawls).then(() => callback(), callback);
|
|
27
|
-
|