@withjoy/limiter 0.0.5-test2 → 0.0.8
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/.editorconfig +28 -0
- package/.nvmrc +1 -0
- package/README.md +22 -0
- package/limiter.js +46 -10
- package/package.json +1 -1
- package/tests/limiter.test.js +119 -8
- package/.npmignore +0 -63
package/.editorconfig
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Look for an EditorConfig plugin for your tool of choice,
|
|
2
|
+
# or you might not be able to commit code ;)
|
|
3
|
+
|
|
4
|
+
# http://editorconfig.org
|
|
5
|
+
|
|
6
|
+
# top-most EditorConfig file, as we could have one per folder
|
|
7
|
+
root = true
|
|
8
|
+
|
|
9
|
+
# All files
|
|
10
|
+
# - use UTF-8
|
|
11
|
+
# - use Unix-style newlines with a newline ending every file
|
|
12
|
+
# - indent by spaces
|
|
13
|
+
# - get whitespace-trimmed
|
|
14
|
+
[*]
|
|
15
|
+
charset = utf-8
|
|
16
|
+
end_of_line = lf
|
|
17
|
+
insert_final_newline = true
|
|
18
|
+
indent_style = space
|
|
19
|
+
trim_trailing_whitespace = true
|
|
20
|
+
|
|
21
|
+
# JavaScript, JSON
|
|
22
|
+
[*.{js,json}]
|
|
23
|
+
indent_style = tab
|
|
24
|
+
indent_size = 2
|
|
25
|
+
|
|
26
|
+
# Markdown
|
|
27
|
+
[*.md]
|
|
28
|
+
indent_size = 2
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
10
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
### A wrapper for limitd and limitdctl
|
|
2
|
+
|
|
3
|
+
##### Why ?
|
|
4
|
+
|
|
5
|
+
Provides utilities and rollback functionality for our clients.
|
|
6
|
+
|
|
7
|
+
### Testing:
|
|
8
|
+
|
|
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 config at `/tests/limitd.config`.
|
|
11
|
+
|
|
12
|
+
##### How can I run that limitd instance quickly ?
|
|
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:
|
|
17
|
+
```bash
|
|
18
|
+
docker run -p 9231:9231 \
|
|
19
|
+
--mount \
|
|
20
|
+
type=bind,source="$(pwd)"/tests/limitd.config,target=/etc/limitd.config \
|
|
21
|
+
-it joylife/limitd:release-6-d5e4805
|
|
22
|
+
```
|
package/limiter.js
CHANGED
|
@@ -3,31 +3,67 @@
|
|
|
3
3
|
|
|
4
4
|
var LimitdClient = require("limitd-client");
|
|
5
5
|
|
|
6
|
+
class Operation {
|
|
7
|
+
constructor({opname,type,id,amount}){
|
|
8
|
+
this.opname = opname;
|
|
9
|
+
this.type = type;
|
|
10
|
+
this.id = id;
|
|
11
|
+
this.amount = amount;
|
|
12
|
+
}
|
|
13
|
+
clone() {
|
|
14
|
+
return new Operation({
|
|
15
|
+
opname: this.opname,
|
|
16
|
+
type: this.type,
|
|
17
|
+
id: this.id,
|
|
18
|
+
amount: this.amount,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// global singleton
|
|
24
|
+
let connection = null;
|
|
25
|
+
|
|
6
26
|
class Limiter {
|
|
7
27
|
|
|
8
28
|
constructor(config) {
|
|
9
|
-
this._limitd = new LimitdClient(config.limitdUrl);
|
|
10
29
|
this._console = config.console;
|
|
11
|
-
|
|
12
30
|
this._operationList = [];
|
|
31
|
+
this._limitd = undefined;
|
|
32
|
+
this.connect(config.limitdUrl);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
The LimitD Client will auto-connect,
|
|
37
|
+
and we invoke this method from the Constructor.
|
|
38
|
+
stub it to prevent a real connect from happening under Test Suite Conditions
|
|
39
|
+
*/
|
|
40
|
+
connect(url) {
|
|
41
|
+
if(!connection) {
|
|
42
|
+
connection = new LimitdClient(url);
|
|
43
|
+
this._limitd = connection;
|
|
44
|
+
}
|
|
45
|
+
return connection;
|
|
13
46
|
}
|
|
14
47
|
|
|
15
48
|
/*
|
|
16
49
|
Go through the log of things we did and do the opposite
|
|
17
50
|
*/
|
|
18
51
|
revert() {
|
|
52
|
+
let ops = this._operationList.map(op => op.clone());
|
|
53
|
+
this._operationList = [];
|
|
54
|
+
|
|
19
55
|
let p = Promise.resolve();
|
|
20
|
-
for (let op of
|
|
56
|
+
for (let op of ops) {
|
|
21
57
|
switch (op.opname) {
|
|
22
58
|
case 'put':
|
|
23
|
-
p.then(() => this._take(op.type, op.id, op.amount));
|
|
59
|
+
p = p.then(() => this._take(op.type, op.id, op.amount));
|
|
24
60
|
break;
|
|
25
61
|
case 'take':
|
|
26
|
-
p.then(() => this._put(op.type, op.id, op.amount));
|
|
62
|
+
p = p.then(() => this._put(op.type, op.id, op.amount));
|
|
27
63
|
break;
|
|
28
64
|
}
|
|
29
65
|
}
|
|
30
|
-
return p
|
|
66
|
+
return p;
|
|
31
67
|
}
|
|
32
68
|
|
|
33
69
|
// just add something to the operations log
|
|
@@ -43,7 +79,7 @@ class Limiter {
|
|
|
43
79
|
this._console.error(err);
|
|
44
80
|
return reject(err);
|
|
45
81
|
}
|
|
46
|
-
this.record({opname: "put",type,id,amount});
|
|
82
|
+
this.record(new Operation({opname: "put",type,id,amount}));
|
|
47
83
|
resolve(null);
|
|
48
84
|
})
|
|
49
85
|
);
|
|
@@ -51,7 +87,7 @@ class Limiter {
|
|
|
51
87
|
|
|
52
88
|
// promise wrapper for limitd lib take
|
|
53
89
|
_take (type, id, amount) {
|
|
54
|
-
return new Promise((resolve, reject) =>
|
|
90
|
+
return new Promise((resolve, reject) =>
|
|
55
91
|
this._limitd.take(type, id, amount, (err, resp) => {
|
|
56
92
|
if (err) {
|
|
57
93
|
this._console.error(err);
|
|
@@ -63,7 +99,7 @@ class Limiter {
|
|
|
63
99
|
return reject(new Error("Non Conformant"));
|
|
64
100
|
}
|
|
65
101
|
|
|
66
|
-
this.record({opname: "take",type,id,amount});
|
|
102
|
+
this.record(new Operation({opname: "take",type,id,amount}));
|
|
67
103
|
return resolve(null);
|
|
68
104
|
})
|
|
69
105
|
)
|
|
@@ -84,7 +120,7 @@ class Limiter {
|
|
|
84
120
|
transferAll(arr) {
|
|
85
121
|
return Promise.all(
|
|
86
122
|
arr.map(spec => this.transfer(spec[0], spec[1], spec[2]))
|
|
87
|
-
).then();
|
|
123
|
+
).then(() => null);
|
|
88
124
|
}
|
|
89
125
|
}
|
|
90
126
|
|
package/package.json
CHANGED
package/tests/limiter.test.js
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
const Limiter = require('../limiter');
|
|
2
2
|
|
|
3
|
+
const PUT_FAILED = "Failed to put!";
|
|
4
|
+
const TAKE_FAILED = "Failed to take!";
|
|
3
5
|
/*
|
|
4
6
|
The limitd service is essentially a token bank
|
|
5
7
|
that we can withdraw from (take) or deposit to (put)
|
|
6
8
|
*/
|
|
7
|
-
const limiterWithMockService = () => {
|
|
9
|
+
const limiterWithMockService = (succeeds = true, conforms = true) => {
|
|
8
10
|
let token_pile = 0;
|
|
9
11
|
const limiter = new Limiter({ console: console });
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
|
|
13
|
+
limiter._limitd = (new class MockLimitd {
|
|
14
|
+
constructor(should_succeed, should_conform) {
|
|
15
|
+
this.should_succeed = should_succeed;
|
|
16
|
+
this.should_conform = should_conform;
|
|
17
|
+
}
|
|
18
|
+
put(type, id, amount, callback) {
|
|
12
19
|
console.log("called put", type, id, amount);
|
|
20
|
+
console.log(this.should_succeed)
|
|
21
|
+
if(!this.should_succeed){
|
|
22
|
+
return callback(new Error(PUT_FAILED), "This is not falsey");
|
|
23
|
+
}
|
|
13
24
|
token_pile += amount;
|
|
14
25
|
callback(null, {})
|
|
15
|
-
}
|
|
16
|
-
take
|
|
26
|
+
}
|
|
27
|
+
take(type, id, amount, callback) {
|
|
17
28
|
console.log("called take", type, id, amount);
|
|
29
|
+
if(!this.should_succeed){
|
|
30
|
+
return callback(new Error(TAKE_FAILED), null);
|
|
31
|
+
}else if (!this.should_conform) {
|
|
32
|
+
return callback(null, {}); // non coformant
|
|
33
|
+
}
|
|
18
34
|
token_pile -= amount;
|
|
19
35
|
callback(null, { conformant: true })
|
|
20
|
-
}
|
|
21
|
-
_num_tokens
|
|
22
|
-
}
|
|
36
|
+
}
|
|
37
|
+
_num_tokens(){ return token_pile }
|
|
38
|
+
}(succeeds, conforms))
|
|
23
39
|
return limiter;
|
|
24
40
|
}
|
|
25
41
|
|
|
@@ -98,4 +114,99 @@ test("Reverts a series of actions when revert is called", done => {
|
|
|
98
114
|
expect(limiter._limitd._num_tokens()).toBe(0);
|
|
99
115
|
done();
|
|
100
116
|
})
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("Properly executes a transferAll()", done => {
|
|
120
|
+
let limiter = limiterWithMockService();
|
|
121
|
+
limiter.transferAll([
|
|
122
|
+
["a", "b", -5],
|
|
123
|
+
["a", "b", 2]
|
|
124
|
+
])
|
|
125
|
+
.then(result => {
|
|
126
|
+
expect(result).toBe(null)
|
|
127
|
+
expect(limiter._limitd._num_tokens()).toBe(3);
|
|
128
|
+
})
|
|
129
|
+
.then(() => limiter.revert())
|
|
130
|
+
.then(() => {
|
|
131
|
+
expect(limiter._limitd._num_tokens()).toBe(0);
|
|
132
|
+
done();
|
|
133
|
+
})
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("Errors from the underlying service are propagated (put)", () => {
|
|
137
|
+
let limiter = limiterWithMockService(succeeds=false);
|
|
138
|
+
let endsInFailure = limiter.transfer("a", "b", -5);
|
|
139
|
+
return expect(endsInFailure).rejects.toThrow(PUT_FAILED);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("Errors from the underlying service are propagated (take)", () => {
|
|
143
|
+
let limiter = limiterWithMockService(succeeds=false);
|
|
144
|
+
let endsInFailure = limiter.transfer("a", "b", 5);
|
|
145
|
+
return expect(endsInFailure).rejects.toThrow(TAKE_FAILED);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("Non-conforming response results in an error", () => {
|
|
149
|
+
let limiter = limiterWithMockService(succeeds=true, conforms=false);
|
|
150
|
+
let endsInFailure = limiter.transfer("a", "b", 5);
|
|
151
|
+
return expect(endsInFailure).rejects.toThrow("Non Conformant");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("An operation that fails is not logged", () => {
|
|
155
|
+
let limiter = limiterWithMockService();
|
|
156
|
+
limiter.transfer("a", "b", -5)
|
|
157
|
+
.then(result => {
|
|
158
|
+
expect(result).toBe(null)
|
|
159
|
+
expect(limiter._limitd._num_tokens()).toBe(5);
|
|
160
|
+
limiter.succeeds = false; // somebody is playing games
|
|
161
|
+
})
|
|
162
|
+
.then(() => limiter.transfer("a", "b", 2))
|
|
163
|
+
.catch(() => "failed uniquely!") // do nothing!
|
|
164
|
+
.then(result => {
|
|
165
|
+
expect(result).toBe("failed uniquely!")
|
|
166
|
+
expect(limiter._limitd._num_tokens()).toBe(5);
|
|
167
|
+
limiter.succeeds = true; // for real this time
|
|
168
|
+
})
|
|
169
|
+
.then(() => limiter.revert())
|
|
170
|
+
.then(() => {
|
|
171
|
+
expect(limiter._limitd._num_tokens()).toBe(0);
|
|
172
|
+
done();
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("Properly handles spamming revert() calls", done => {
|
|
177
|
+
let limiter = limiterWithMockService();
|
|
178
|
+
limiter.transfer("a", "b", -5)
|
|
179
|
+
.then(result => {
|
|
180
|
+
expect(result).toBe(null)
|
|
181
|
+
expect(limiter._limitd._num_tokens()).toBe(5);
|
|
182
|
+
})
|
|
183
|
+
.then(() => limiter.transfer("a", "b", 2))
|
|
184
|
+
.then(result => {
|
|
185
|
+
expect(result).toBe(null)
|
|
186
|
+
expect(limiter._limitd._num_tokens()).toBe(3);
|
|
187
|
+
})
|
|
188
|
+
.then(() =>
|
|
189
|
+
Promise.all([
|
|
190
|
+
limiter.revert(),
|
|
191
|
+
limiter.revert(),
|
|
192
|
+
limiter.revert(),
|
|
193
|
+
limiter.revert(),
|
|
194
|
+
limiter.revert(),
|
|
195
|
+
limiter.revert(),
|
|
196
|
+
limiter.revert(),
|
|
197
|
+
limiter.revert(),
|
|
198
|
+
limiter.revert(),
|
|
199
|
+
limiter.revert(),
|
|
200
|
+
limiter.revert(),
|
|
201
|
+
limiter.revert(),
|
|
202
|
+
limiter.revert(),
|
|
203
|
+
limiter.revert(),
|
|
204
|
+
limiter.revert(),
|
|
205
|
+
limiter.revert(),
|
|
206
|
+
])
|
|
207
|
+
)
|
|
208
|
+
.then(() => {
|
|
209
|
+
expect(limiter._limitd._num_tokens()).toBe(0);
|
|
210
|
+
done();
|
|
211
|
+
})
|
|
101
212
|
});
|
package/.npmignore
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Logs
|
|
2
|
-
logs
|
|
3
|
-
*.log
|
|
4
|
-
npm-debug.log*
|
|
5
|
-
yarn-debug.log*
|
|
6
|
-
yarn-error.log*
|
|
7
|
-
|
|
8
|
-
# Runtime data
|
|
9
|
-
pids
|
|
10
|
-
*.pid
|
|
11
|
-
*.seed
|
|
12
|
-
*.pid.lock
|
|
13
|
-
|
|
14
|
-
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
15
|
-
lib-cov
|
|
16
|
-
|
|
17
|
-
# Coverage directory used by tools like istanbul
|
|
18
|
-
coverage
|
|
19
|
-
|
|
20
|
-
# nyc test coverage
|
|
21
|
-
.nyc_output
|
|
22
|
-
|
|
23
|
-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
24
|
-
.grunt
|
|
25
|
-
|
|
26
|
-
# Bower dependency directory (https://bower.io/)
|
|
27
|
-
bower_components
|
|
28
|
-
|
|
29
|
-
# node-waf configuration
|
|
30
|
-
.lock-wscript
|
|
31
|
-
|
|
32
|
-
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
33
|
-
build/Release
|
|
34
|
-
|
|
35
|
-
# Dependency directories
|
|
36
|
-
node_modules/
|
|
37
|
-
jspm_packages/
|
|
38
|
-
|
|
39
|
-
# Typescript v1 declaration files
|
|
40
|
-
typings/
|
|
41
|
-
|
|
42
|
-
# Optional npm cache directory
|
|
43
|
-
.npm
|
|
44
|
-
|
|
45
|
-
# Optional eslint cache
|
|
46
|
-
.eslintcache
|
|
47
|
-
|
|
48
|
-
# Optional REPL history
|
|
49
|
-
.node_repl_history
|
|
50
|
-
|
|
51
|
-
# Output of 'npm pack'
|
|
52
|
-
*.tgz
|
|
53
|
-
|
|
54
|
-
# Yarn Integrity file
|
|
55
|
-
.yarn-integrity
|
|
56
|
-
|
|
57
|
-
# dotenv environment variables file
|
|
58
|
-
.env
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
#db
|
|
62
|
-
db
|
|
63
|
-
tests/db
|