@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 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 this._operationList) {
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.then(() => this._operationList = []);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withjoy/limiter",
3
- "version": "0.0.5-test2",
3
+ "version": "0.0.8",
4
4
  "description": "Api Rate limiter",
5
5
  "main": "limiter.js",
6
6
  "scripts": {
@@ -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
- limiter._limitd = {
11
- put: function (type, id, amount, callback) {
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: function (type, id, amount, callback) {
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: () => token_pile
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