Skip to content

Commit ba1f177

Browse files
authored
Merge pull request #72 from Luidog/sessions-testing
Session testing
2 parents 941e76e + bf3e8aa commit ba1f177

File tree

7 files changed

+303
-36
lines changed

7 files changed

+303
-36
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "A FileMaker Data API client designed to allow easier interaction with a FileMaker database from a web environment.",
55
"main": "index.js",
66
"scripts": {
7-
"test": "nyc _mocha --recursive ./test/*.test.js --timeout=15000 --exit",
7+
"test": "nyc _mocha --recursive ./test/*.test.js --timeout=40000 --exit",
88
"coverage": "nyc report --reporter=text-lcov | coveralls",
99
"report": "nyc report --reporter=html",
1010
"examples": "node examples/index.js",

src/models/agent.model.js

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -210,20 +210,29 @@ class Agent extends EmbeddedDocument {
210210
* @return {Object} request The configured axios instance to use for a request.
211211
*/
212212
request(data, parameters = {}) {
213-
instance.interceptors.request.use(
214-
({ httpAgent, httpsAgent, ...request }) =>
215-
new Promise((resolve, reject) =>
213+
const id = uuidv4();
214+
const interceptor = instance.interceptors.request.use(
215+
({ httpAgent, httpsAgent, ...request }) => {
216+
instance.interceptors.request.eject(interceptor);
217+
return new Promise((resolve, reject) =>
216218
this.push({
217-
request: this.handleRequest(request),
219+
request: this.handleRequest(request, id),
218220
resolve,
219221
reject
220222
})
221-
)
223+
);
224+
}
222225
);
223226

224-
instance.interceptors.response.use(
225-
response => this.handleResponse(response),
226-
error => this.handleError(error)
227+
const response = instance.interceptors.response.use(
228+
response => {
229+
instance.interceptors.response.eject(response);
230+
return this.handleResponse(response, id);
231+
},
232+
error => {
233+
instance.interceptors.response.eject(response);
234+
return this.handleError(error, id);
235+
}
227236
);
228237

229238
return instance(
@@ -244,17 +253,22 @@ class Agent extends EmbeddedDocument {
244253
* @description handles request data before it is sent to the resource. This function
245254
* will eventually be used to cancel the request and return the configuration body.
246255
* This function will test the url for an http proticol and reject if none exist.
247-
* @param {Object} response The axios response
256+
* @param {Object} response The axios response.
257+
* @param {String} id the request id.
248258
* @return {Promise} the request configuration object
249259
*/
250-
handleResponse(response) {
260+
handleResponse(response, id) {
261+
const token = _.get(response, 'config.headers.Authorization');
262+
if (token) {
263+
this.connection.deactivate(token, id);
264+
}
251265
if (typeof response.data !== 'object') {
252266
return Promise.reject({
253267
message: 'The Data API is currently unavailable',
254268
code: '1630'
255269
});
256270
} else {
257-
this.connection.extend(response.config.headers.Authorization);
271+
this.connection.extend(token);
258272
return response;
259273
}
260274
}
@@ -266,10 +280,12 @@ class Agent extends EmbeddedDocument {
266280
* @description handles request data before it is sent to the resource. This function
267281
* will eventually be used to cancel the request and return the configuration body.
268282
* This function will test the url for an http proticol and reject if none exist.
269-
* @param {Object} config The axios request configuration
283+
* @param {Object} config The axios request configuration.
284+
* @param {String} id the request id.
270285
* @return {Promise} the request configuration object
271286
*/
272-
handleRequest(config) {
287+
handleRequest(config, id) {
288+
config.id = id;
273289
return config.url.startsWith('http')
274290
? omit(config, ['params.request', 'data.request'])
275291
: Promise.reject({
@@ -360,9 +376,17 @@ class Agent extends EmbeddedDocument {
360376
* function will add an expired property to the error response if it recieves a invalid
361377
* token response.
362378
* @param {Object} error The error recieved from the requested resource.
379+
* @param {String} id the request id.
363380
* @return {Promise} A promise rejection containing a code and a message
364381
*/
365-
handleError(error) {
382+
handleError(error, id) {
383+
const token = _.get(error, 'config.headers.Authorization');
384+
if (token) {
385+
this.connection.deactivate(token, id);
386+
}
387+
388+
this.connection.confirm();
389+
366390
if (error.code) {
367391
return Promise.reject({ code: error.code, message: error.message });
368392
} else if (
@@ -375,9 +399,7 @@ class Agent extends EmbeddedDocument {
375399
});
376400
} else {
377401
if (error.response.data.messages[0].code === '952')
378-
this.connection.clear(
379-
_.get(error, 'response.config.headers.Authorization')
380-
);
402+
this.connection.clear(token);
381403
return Promise.reject(error.response.data.messages[0]);
382404
}
383405
}
@@ -401,15 +423,24 @@ class Agent extends EmbeddedDocument {
401423
const WATCHER = setTimeout(
402424
function watch() {
403425
this.connection.clear();
404-
if (this.queue.length > 0) {
426+
if (
427+
this.queue.length > 0 &&
428+
!this.connection.starting &&
429+
this.connection.available()
430+
) {
405431
this.shift();
406432
}
407433

408-
if (this.pending.length > 0 && this.connection.ready()) {
434+
if (
435+
this.pending.length > 0 &&
436+
!this.connection.starting &&
437+
this.connection.available()
438+
) {
409439
this.resolve();
410440
}
411441

412442
if (
443+
this.pending.length > 0 &&
413444
!this.connection.available() &&
414445
!this.connection.starting &&
415446
this.connection.sessions.length < this.concurrency
@@ -453,7 +484,8 @@ class Agent extends EmbeddedDocument {
453484
Object.assign(
454485
this.mutate(pending.request, (value, key) =>
455486
key.replace(/{{dot}}/g, '.')
456-
)
487+
),
488+
{ id: pending.id }
457489
),
458490
_.isEmpty(this.agent) ? {} : this.localize()
459491
)

src/models/connection.model.js

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class Connection extends EmbeddedDocument {
104104
* @see {@link Connection#available}
105105
* @return {String} The session token.
106106
*/
107-
authentication({ headers, ...request }) {
107+
authentication({ headers, id, ...request }) {
108108
return new Promise((resolve, reject) => {
109109
const sessions = _.sortBy(
110110
this.sessions.filter(session => !session.expired()),
@@ -114,6 +114,7 @@ class Connection extends EmbeddedDocument {
114114
const session = sessions[0];
115115
session.active = true;
116116
session.url = request.url;
117+
session.request = id;
117118
session.used = moment().format();
118119
resolve({
119120
...request,
@@ -125,18 +126,6 @@ class Connection extends EmbeddedDocument {
125126
});
126127
}
127128

128-
/**
129-
* @method ready
130-
* @public
131-
* @memberof Connection
132-
* @description Saves a token retrieved from the Data API as a sessions
133-
* @see {@link session}
134-
* @return {Boolean} data a boolean indicating if the connection has a session.
135-
*/
136-
ready() {
137-
return this.sessions.filter(session => !session.expired()).length > 0;
138-
}
139-
140129
/**
141130
* @method available
142131
* @public
@@ -287,6 +276,38 @@ class Connection extends EmbeddedDocument {
287276
const session = _.find(this.sessions, session => session.token === token);
288277
if (session) session.extend();
289278
}
279+
280+
/**
281+
* @method confirm
282+
* @memberOf confirm
283+
* @description The confirm method will set the active property to false when a session does not have a
284+
* valid request id.
285+
*/
286+
confirm() {
287+
this.sessions.forEach(session => {
288+
if (_.isEmpty(session.request)) {
289+
session.active = false;
290+
}
291+
});
292+
}
293+
/**
294+
* @method deactivate
295+
* @memberOf Connection
296+
* @public
297+
* @description The deactivate method will reactive a session by setting the active property to false.
298+
* @param {String} header The header containing the token representing the session to deactivate
299+
* @param {String} id The request id.
300+
* @see {@link Agent#handleResponse}
301+
* @see {@link Agent#handleError}
302+
*/
303+
deactivate(header, id) {
304+
const token = header.replace('Bearer ', '');
305+
const session = _.find(
306+
this.sessions,
307+
session => session.token === token || session.request === id
308+
);
309+
if (session) session.deactivate();
310+
}
290311
}
291312

292313
module.exports = {

src/models/session.model.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ class Session extends EmbeddedDocument {
4343
used: {
4444
type: String
4545
},
46+
/* A string containing the request created when the token is in use..
47+
* @member Session#request
48+
* @type String
49+
*/
50+
request: {
51+
type: String
52+
},
4653
/* A boolean set if the current session is in use.
4754
* @member Session#active
4855
* @type Boolean
@@ -95,13 +102,26 @@ class Session extends EmbeddedDocument {
95102
* @method extend
96103
* @memberof Session
97104
* @public
98-
* @description This method extends a Data API session and sets it to inactive.
105+
* @description This method extends a Data API session.
99106
* @see {@link Agent#handleResponse}
100107
*/
101108
extend() {
102109
this.active = false;
103110
this.expires = moment().add(15, 'minutes').format();
104111
}
112+
113+
/**
114+
* @method deactivate
115+
* @memberOf Sessions
116+
* @public
117+
* @description This method sets deactivates a session by setting active to false.
118+
* @see {@link Agent#handleResponse}
119+
* @see {@link Agent#handleError}
120+
*/
121+
deactivate() {
122+
this.request = "";
123+
this.active = false;
124+
}
105125
}
106126

107127
module.exports = {

test/admin/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@ const login = () =>
3030
return response.data.response.token;
3131
});
3232

33-
const logout = (token = adminToken) => instance
33+
const logout = (token = adminToken) =>
34+
token
35+
? instance
3436
.delete(`/fmi/admin/api/v2/user/auth/${token}`, {})
37+
.then(response => {
38+
adminToken = false;
39+
return response.data.response.token;
40+
})
41+
: Promise.resolve();
3542

3643
const remove = ({ id }, token = adminToken) =>
3744
instance

test/queue.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ describe('Request Queue Capabilities', () => {
4242
client = Filemaker.create({
4343
database: process.env.DATABASE,
4444
server: process.env.SERVER,
45+
concurrency: 25,
4546
user: process.env.USERNAME,
4647
password: process.env.PASSWORD
4748
});

0 commit comments

Comments
 (0)