@anmiles/google-api-wrapper 9.0.0 → 11.0.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/.eslintrc.js +3 -0
- package/CHANGELOG.md +33 -0
- package/README.md +12 -7
- package/dist/index.d.ts +1 -3
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +306 -0
- package/dist/lib/api.js +336 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/secrets.d.ts +3 -1
- package/dist/lib/secrets.js +30 -10
- package/dist/lib/secrets.js.map +1 -1
- package/package.json +3 -1
- package/src/index.ts +1 -3
- package/src/lib/__tests__/api.test.ts +206 -0
- package/src/lib/__tests__/secrets.test.ts +132 -114
- package/src/lib/api.ts +365 -0
- package/src/lib/secrets.ts +34 -11
- package/tsconfig.json +1 -0
- package/dist/lib/api/calendar.d.ts +0 -3
- package/dist/lib/api/calendar.js +0 -14
- package/dist/lib/api/calendar.js.map +0 -1
- package/dist/lib/api/shared.d.ts +0 -23
- package/dist/lib/api/shared.js +0 -27
- package/dist/lib/api/shared.js.map +0 -1
- package/dist/lib/api/youtube.d.ts +0 -3
- package/dist/lib/api/youtube.js +0 -14
- package/dist/lib/api/youtube.js.map +0 -1
- package/src/lib/api/__tests__/calendar.test.ts +0 -45
- package/src/lib/api/__tests__/shared.test.ts +0 -93
- package/src/lib/api/__tests__/youtube.test.ts +0 -45
- package/src/lib/api/calendar.ts +0 -13
- package/src/lib/api/shared.ts +0 -44
- package/src/lib/api/youtube.ts +0 -13
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import logger from '@anmiles/logger';
|
|
3
|
+
import sleep from '@anmiles/sleep';
|
|
4
|
+
import auth from '../auth';
|
|
5
|
+
import secrets from '../secrets';
|
|
6
|
+
import api from '../api';
|
|
7
|
+
|
|
8
|
+
const items: Array<{ data: string}> = [
|
|
9
|
+
{ data : 'first' },
|
|
10
|
+
{ data : 'second' },
|
|
11
|
+
{ data : 'third' },
|
|
12
|
+
{ data : 'forth' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const response = [
|
|
16
|
+
[ items[0], items[1] ],
|
|
17
|
+
null,
|
|
18
|
+
[ items[2], items[3] ],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const pageTokens = [
|
|
22
|
+
undefined,
|
|
23
|
+
'token1',
|
|
24
|
+
'token2',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const getAPI = <T>(items: Array<Array<T> | null>, pageTokens: Array<string | undefined>) => ({
|
|
28
|
+
list : jest.fn().mockImplementation(async ({ pageToken }: {pageToken?: string}) => {
|
|
29
|
+
const listException = getListException();
|
|
30
|
+
|
|
31
|
+
if (listException) {
|
|
32
|
+
throw listException;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const index = pageTokens.indexOf(pageToken);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
data : {
|
|
39
|
+
items : items[index],
|
|
40
|
+
nextPageToken : pageTokens[index + 1],
|
|
41
|
+
pageInfo : !items[index] ? null : {
|
|
42
|
+
totalResults : items.reduce((sum, list) => sum + (list?.length || 0), 0),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}),
|
|
47
|
+
update : jest.fn(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const args = { key : 'value' };
|
|
51
|
+
|
|
52
|
+
const profile = 'username1';
|
|
53
|
+
const calendarApi = {
|
|
54
|
+
calendarList : getAPI(response, pageTokens),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const googleAuth = {
|
|
58
|
+
revokeCredentials : jest.fn(),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const scopes = [ 'scope1', 'scope2' ];
|
|
62
|
+
|
|
63
|
+
const getListException: jest.Mock<Error | undefined> = jest.fn();
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
getListException.mockReturnValue(undefined);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
jest.mock('googleapis', () => ({
|
|
70
|
+
google : {
|
|
71
|
+
calendar : jest.fn().mockImplementation(() => calendarApi),
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
jest.mock<Partial<typeof auth>>('../auth', () => ({
|
|
76
|
+
getAuth : jest.fn().mockImplementation(() => googleAuth),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
jest.mock<Partial<typeof secrets>>('../secrets', () => ({
|
|
80
|
+
deleteCredentials : jest.fn(),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
jest.mock<Partial<typeof logger>>('@anmiles/logger', () => ({
|
|
84
|
+
log : jest.fn(),
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
jest.mock<Partial<typeof sleep>>('@anmiles/sleep', () => jest.fn());
|
|
88
|
+
|
|
89
|
+
describe('src/lib/api', () => {
|
|
90
|
+
describe('getApi', () => {
|
|
91
|
+
it('should call getAuth', async () => {
|
|
92
|
+
await api.getApi('calendar', profile);
|
|
93
|
+
|
|
94
|
+
expect(auth.getAuth).toHaveBeenCalledWith(profile, undefined);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should pass temporariness and scopes', async () => {
|
|
98
|
+
await api.getApi('calendar', profile, { scopes, temporary : true });
|
|
99
|
+
|
|
100
|
+
expect(auth.getAuth).toHaveBeenCalledWith(profile, { scopes, temporary : true });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should get google api', async () => {
|
|
104
|
+
await api.getApi('calendar', profile);
|
|
105
|
+
|
|
106
|
+
expect(google.calendar).toHaveBeenCalledWith({ version : 'v3', auth : googleAuth });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should return instance wrapper for google api', async () => {
|
|
110
|
+
const instance = await api.getApi('calendar', profile, { scopes, temporary : true });
|
|
111
|
+
|
|
112
|
+
expect(instance).toEqual({ apiName : 'calendar', profile, authOptions : { scopes, temporary : true }, api : calendarApi, auth : googleAuth });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Api', () => {
|
|
117
|
+
let instance: InstanceType<typeof api.Api<'calendar'>>;
|
|
118
|
+
|
|
119
|
+
beforeEach(async () => {
|
|
120
|
+
instance = await api.getApi('calendar', profile);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('constructor', () => {
|
|
124
|
+
it('should return instance', async () => {
|
|
125
|
+
const instance = new api.Api('calendar', profile, { scopes, temporary : true });
|
|
126
|
+
|
|
127
|
+
expect(instance).toEqual({ apiName : 'calendar', profile, authOptions : { scopes, temporary : true } });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('getItems', () => {
|
|
132
|
+
it('should call API list method for each page', async () => {
|
|
133
|
+
await instance.getItems((api) => api.calendarList, args);
|
|
134
|
+
|
|
135
|
+
pageTokens.forEach((pageToken) => {
|
|
136
|
+
expect(calendarApi.calendarList.list).toHaveBeenCalledWith({ ...args, pageToken });
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should output progress by default', async () => {
|
|
141
|
+
await instance.getItems((api) => api.calendarList, args);
|
|
142
|
+
|
|
143
|
+
expect(logger.log).toHaveBeenCalledTimes(response.length);
|
|
144
|
+
expect(logger.log).toHaveBeenCalledWith('Getting items (2 of 4)...');
|
|
145
|
+
expect(logger.log).toHaveBeenCalledWith('Getting items (2 of many)...');
|
|
146
|
+
expect(logger.log).toHaveBeenCalledWith('Getting items (4 of 4)...');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should not output progress if hidden', async () => {
|
|
150
|
+
await instance.getItems((api) => api.calendarList, args, { hideProgress : true });
|
|
151
|
+
|
|
152
|
+
expect(logger.log).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should sleep after reach request', async () => {
|
|
156
|
+
await instance.getItems((api) => api.calendarList, args);
|
|
157
|
+
|
|
158
|
+
expect(sleep).toHaveBeenCalledTimes(response.length);
|
|
159
|
+
expect(sleep).toHaveBeenCalledWith(300);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should be initialized and called once if no API error', async () => {
|
|
163
|
+
const getItemsSpy = jest.spyOn(instance, 'getItems');
|
|
164
|
+
await instance.getItems((api) => api.calendarList, args);
|
|
165
|
+
expect(auth.getAuth).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(getItemsSpy).toHaveBeenCalledTimes(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should delete credentials, re-initialize api and retry while API exception is invalid_grant', async () => {
|
|
170
|
+
const error = new Error('invalid_grant');
|
|
171
|
+
// fail twice
|
|
172
|
+
getListException.mockReturnValueOnce(error).mockReturnValueOnce(error);
|
|
173
|
+
|
|
174
|
+
const getItemsSpy = jest.spyOn(instance, 'getItems');
|
|
175
|
+
await instance.getItems((api) => api.calendarList, args);
|
|
176
|
+
expect(secrets.deleteCredentials).toHaveBeenCalledWith(profile);
|
|
177
|
+
expect(auth.getAuth).toHaveBeenCalledTimes(3);
|
|
178
|
+
expect(getItemsSpy).toHaveBeenCalledTimes(3);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should re-throw API exception if not invalid_grant', async () => {
|
|
182
|
+
const error = new Error('random exception');
|
|
183
|
+
getListException.mockReturnValueOnce(error);
|
|
184
|
+
await expect(instance.getItems((api) => api.calendarList, args)).rejects.toEqual(error);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should return items data', async () => {
|
|
188
|
+
const items = await instance.getItems((api) => api.calendarList, args);
|
|
189
|
+
|
|
190
|
+
expect(items).toEqual(items);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('revoke', () => {
|
|
195
|
+
it('should delete credentials file for current profile', async () => {
|
|
196
|
+
await instance.revoke();
|
|
197
|
+
expect(secrets.deleteCredentials).toHaveBeenCalledWith(profile);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should revoke credentials in google API', async () => {
|
|
201
|
+
await instance.revoke();
|
|
202
|
+
expect(googleAuth.revokeCredentials).toHaveBeenCalledWith();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import open from 'open';
|
|
5
5
|
import type GoogleApis from 'googleapis';
|
|
6
6
|
import logger from '@anmiles/logger';
|
|
7
|
+
import emitter from 'event-emitter';
|
|
7
8
|
import paths from '../paths';
|
|
8
9
|
import type { Secrets } from '../../types';
|
|
9
10
|
import '@anmiles/prototypes';
|
|
@@ -16,26 +17,31 @@ jest.mock<typeof secrets>('../secrets', () => ({
|
|
|
16
17
|
getCredentials : jest.fn(),
|
|
17
18
|
validateCredentials : jest.fn(),
|
|
18
19
|
createCredentials : jest.fn().mockImplementation(() => credentialsJSON),
|
|
20
|
+
deleteCredentials : jest.fn(),
|
|
19
21
|
checkSecrets : jest.fn(),
|
|
20
22
|
getScopesError : jest.fn().mockImplementation(() => scopesError),
|
|
21
23
|
getSecretsError : jest.fn().mockImplementation(() => secretsError),
|
|
22
24
|
}));
|
|
23
25
|
|
|
24
26
|
jest.mock<Partial<typeof http>>('http', () => ({
|
|
25
|
-
createServer : jest.fn().mockImplementation((
|
|
26
|
-
serverCallback = callback;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
on,
|
|
30
|
-
listen,
|
|
31
|
-
close,
|
|
32
|
-
destroy,
|
|
33
|
-
};
|
|
34
|
-
}),
|
|
27
|
+
createServer : jest.fn().mockImplementation(() => server),
|
|
35
28
|
}));
|
|
36
29
|
|
|
30
|
+
let server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
31
|
+
let response: http.ServerResponse;
|
|
32
|
+
|
|
33
|
+
async function makeRequest(url: string | undefined) {
|
|
34
|
+
server.emit('request', {
|
|
35
|
+
url,
|
|
36
|
+
headers : {
|
|
37
|
+
host,
|
|
38
|
+
},
|
|
39
|
+
} as http.IncomingMessage, response);
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
jest.mock<Partial<typeof fs>>('fs', () => ({
|
|
38
43
|
existsSync : jest.fn().mockImplementation(() => exists),
|
|
44
|
+
rmSync : jest.fn(),
|
|
39
45
|
}));
|
|
40
46
|
|
|
41
47
|
jest.mock<Partial<typeof path>>('path', () => ({
|
|
@@ -43,7 +49,7 @@ jest.mock<Partial<typeof path>>('path', () => ({
|
|
|
43
49
|
}));
|
|
44
50
|
|
|
45
51
|
jest.mock('open', () => jest.fn().mockImplementation((url: string) => {
|
|
46
|
-
|
|
52
|
+
makeRequest(url.replace('http://localhost:6006', ''));
|
|
47
53
|
}));
|
|
48
54
|
|
|
49
55
|
jest.mock<Partial<typeof logger>>('@anmiles/logger', () => ({
|
|
@@ -56,6 +62,12 @@ jest.mock<Partial<typeof paths>>('../paths', () => ({
|
|
|
56
62
|
getCredentialsFile : jest.fn().mockImplementation(() => credentialsFile),
|
|
57
63
|
}));
|
|
58
64
|
|
|
65
|
+
jest.useFakeTimers();
|
|
66
|
+
|
|
67
|
+
const port = 6006;
|
|
68
|
+
const host = `localhost:${port}`;
|
|
69
|
+
const callbackURI = `http://${host}/oauthcallback`;
|
|
70
|
+
|
|
59
71
|
const profile = 'username1';
|
|
60
72
|
const scopesFile = 'scopes.json';
|
|
61
73
|
const secretsFile = 'secrets/username1.json';
|
|
@@ -79,7 +91,7 @@ const secretsJSON: Secrets = {
|
|
|
79
91
|
token_uri : 'https://oauth2.googleapis.com/token',
|
|
80
92
|
auth_provider_x509_cert_url : 'https://www.googleapis.com/oauth2/v1/certs',
|
|
81
93
|
client_secret : 'client_secret',
|
|
82
|
-
redirect_uris : [
|
|
94
|
+
redirect_uris : [ callbackURI ],
|
|
83
95
|
/* eslint-enable camelcase */
|
|
84
96
|
},
|
|
85
97
|
};
|
|
@@ -98,46 +110,6 @@ const auth = {
|
|
|
98
110
|
getToken : jest.fn().mockResolvedValue({ tokens : credentialsJSON }),
|
|
99
111
|
} as unknown as GoogleApis.Common.OAuth2Client;
|
|
100
112
|
|
|
101
|
-
const response = {
|
|
102
|
-
end : jest.fn(),
|
|
103
|
-
} as unknown as http.ServerResponse;
|
|
104
|
-
|
|
105
|
-
let serverCallback: (
|
|
106
|
-
request: http.IncomingMessage,
|
|
107
|
-
response: http.ServerResponse
|
|
108
|
-
) => Promise<typeof credentialsJSON>;
|
|
109
|
-
|
|
110
|
-
function willOpen(url: string | undefined, timeout?: number) {
|
|
111
|
-
setTimeout(async () => {
|
|
112
|
-
await serverCallback({
|
|
113
|
-
url,
|
|
114
|
-
headers : {
|
|
115
|
-
host : 'localhost:6006',
|
|
116
|
-
},
|
|
117
|
-
} as http.IncomingMessage, response);
|
|
118
|
-
}, timeout || 0);
|
|
119
|
-
}
|
|
120
|
-
let closedTime: number;
|
|
121
|
-
|
|
122
|
-
const on = jest.fn().mockImplementation((event: string, listener: (...args: any[]) => void) => {
|
|
123
|
-
if (event === 'connection') {
|
|
124
|
-
// always simulate opening several connections once connections are meant to be listened
|
|
125
|
-
connections.forEach((connection) => listener(connection));
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const listen = jest.fn();
|
|
130
|
-
const close = jest.fn().mockImplementation(() => {
|
|
131
|
-
closedTime = new Date().getTime();
|
|
132
|
-
});
|
|
133
|
-
const destroy = jest.fn();
|
|
134
|
-
|
|
135
|
-
const connections = [
|
|
136
|
-
{ remoteAddress : 'server', remotePort : '1001', on : jest.fn(), destroy : jest.fn() },
|
|
137
|
-
{ remoteAddress : 'server', remotePort : '1002', on : jest.fn(), destroy : jest.fn() },
|
|
138
|
-
{ remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
|
|
139
|
-
];
|
|
140
|
-
|
|
141
113
|
let exists: boolean;
|
|
142
114
|
|
|
143
115
|
let getJSONSpy: jest.SpyInstance;
|
|
@@ -360,10 +332,38 @@ describe('src/lib/secrets', () => {
|
|
|
360
332
|
describe('createCredentials', () => {
|
|
361
333
|
const tokenUrl = `/request.url?code=${code}`;
|
|
362
334
|
|
|
363
|
-
|
|
364
|
-
|
|
335
|
+
const connections = [
|
|
336
|
+
{ remoteAddress : 'server', remotePort : '1001', on : jest.fn(), destroy : jest.fn() },
|
|
337
|
+
{ remoteAddress : 'server', remotePort : '1002', on : jest.fn(), destroy : jest.fn() },
|
|
338
|
+
{ remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
let endSpy: jest.SpyInstance;
|
|
342
|
+
|
|
343
|
+
beforeEach(() => {
|
|
344
|
+
server = emitter({
|
|
345
|
+
listen : jest.fn().mockImplementation(() => {
|
|
346
|
+
// always simulate opening several connections once connections are meant to be listened
|
|
347
|
+
connections.forEach((connection) => server.emit('connection', connection));
|
|
348
|
+
}),
|
|
349
|
+
close : jest.fn(),
|
|
350
|
+
destroy : jest.fn(),
|
|
351
|
+
}) as typeof server;
|
|
352
|
+
|
|
353
|
+
response = emitter({
|
|
354
|
+
end : jest.fn(),
|
|
355
|
+
}) as typeof response;
|
|
365
356
|
|
|
366
|
-
|
|
357
|
+
endSpy = jest.spyOn(response, 'end');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
afterAll(() => {
|
|
361
|
+
endSpy.mockRestore();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should generate authUrl', async () => {
|
|
365
|
+
original.createCredentials(profile, auth);
|
|
366
|
+
await Promise.resolve();
|
|
367
367
|
|
|
368
368
|
expect(auth.generateAuthUrl).toHaveBeenCalledWith({
|
|
369
369
|
// eslint-disable-next-line camelcase
|
|
@@ -377,9 +377,8 @@ describe('src/lib/secrets', () => {
|
|
|
377
377
|
});
|
|
378
378
|
|
|
379
379
|
it('should generate authUrl and require consent if explicitly asked', async () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
await original.createCredentials(profile, auth, { temporary : true }, 'consent');
|
|
380
|
+
original.createCredentials(profile, auth, { temporary : true }, 'consent');
|
|
381
|
+
await Promise.resolve();
|
|
383
382
|
|
|
384
383
|
expect(auth.generateAuthUrl).toHaveBeenCalledWith({
|
|
385
384
|
// eslint-disable-next-line camelcase
|
|
@@ -393,9 +392,8 @@ describe('src/lib/secrets', () => {
|
|
|
393
392
|
});
|
|
394
393
|
|
|
395
394
|
it('should generate authUrl with custom scopes', async () => {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
await original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
|
|
395
|
+
original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
|
|
396
|
+
await Promise.resolve();
|
|
399
397
|
|
|
400
398
|
expect(auth.generateAuthUrl).toHaveBeenCalledWith({
|
|
401
399
|
// eslint-disable-next-line camelcase
|
|
@@ -406,39 +404,45 @@ describe('src/lib/secrets', () => {
|
|
|
406
404
|
});
|
|
407
405
|
|
|
408
406
|
it('should create server on 6006 port', async () => {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
await original.createCredentials(profile, auth);
|
|
407
|
+
original.createCredentials(profile, auth);
|
|
408
|
+
await Promise.resolve();
|
|
412
409
|
|
|
413
410
|
expect(http.createServer).toHaveBeenCalled();
|
|
414
|
-
expect(listen).toHaveBeenCalledWith(6006);
|
|
411
|
+
expect(server.listen).toHaveBeenCalledWith(6006);
|
|
415
412
|
});
|
|
416
413
|
|
|
417
|
-
it('should open browser page and warn about it', async () => {
|
|
418
|
-
|
|
414
|
+
it('should open browser page and warn about it once listening', async () => {
|
|
415
|
+
original.createCredentials(profile, auth);
|
|
416
|
+
await Promise.resolve();
|
|
419
417
|
|
|
420
|
-
|
|
418
|
+
server.emit('listening');
|
|
421
419
|
|
|
422
420
|
expect(open).toHaveBeenCalledWith('http://localhost:6006/');
|
|
423
421
|
expect(logger.warn).toHaveBeenCalledWith('Please check your browser for further actions');
|
|
424
422
|
});
|
|
425
423
|
|
|
426
|
-
it('should
|
|
427
|
-
|
|
428
|
-
|
|
424
|
+
it('should not open browser page and warn about it until listening', async () => {
|
|
425
|
+
original.createCredentials(profile, auth);
|
|
426
|
+
await Promise.resolve();
|
|
429
427
|
|
|
430
|
-
|
|
428
|
+
expect(open).not.toHaveBeenCalled();
|
|
429
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should show nothing on the browser page if request.url is empty', async () => {
|
|
433
|
+
original.createCredentials(profile, auth);
|
|
434
|
+
makeRequest('');
|
|
435
|
+
await Promise.resolve();
|
|
431
436
|
|
|
432
|
-
expect(
|
|
437
|
+
expect(endSpy).toHaveBeenCalledWith('');
|
|
433
438
|
});
|
|
434
439
|
|
|
435
440
|
it('should show opening instructions if opened the home page', async () => {
|
|
436
|
-
|
|
437
|
-
|
|
441
|
+
original.createCredentials(profile, auth);
|
|
442
|
+
makeRequest('/');
|
|
443
|
+
await Promise.resolve();
|
|
438
444
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
expect(response.end).toHaveBeenCalledWith(`\
|
|
445
|
+
expect(endSpy).toHaveBeenCalledWith(`\
|
|
442
446
|
<div style="width: 100%;height: 100%;display: flex;align-items: start;justify-content: center">\n\
|
|
443
447
|
<div style="padding: 0 1em;border: 1px solid black;font-family: Arial, sans-serif;margin: 1em;">\n\
|
|
444
448
|
<p>Please open <a href="${authUrl}">auth page</a> using <strong>${profile}</strong> google profile</p>\n\
|
|
@@ -449,11 +453,11 @@ describe('src/lib/secrets', () => {
|
|
|
449
453
|
});
|
|
450
454
|
|
|
451
455
|
it('should ask to close webpage', async () => {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
await
|
|
456
|
+
original.createCredentials(profile, auth);
|
|
457
|
+
makeRequest(tokenUrl);
|
|
458
|
+
await Promise.resolve();
|
|
455
459
|
|
|
456
|
-
expect(
|
|
460
|
+
expect(endSpy).toHaveBeenCalledWith('\
|
|
457
461
|
<div style="width: 100%;height: 100%;display: flex;align-items: start;justify-content: center">\n\
|
|
458
462
|
<div style="padding: 0 1em;border: 1px solid black;font-family: Arial, sans-serif;margin: 1em;">\n\
|
|
459
463
|
<p>Please close this page and return to application</p>\n\
|
|
@@ -462,58 +466,72 @@ describe('src/lib/secrets', () => {
|
|
|
462
466
|
});
|
|
463
467
|
|
|
464
468
|
it('should close server and destroy all connections if request.url is truthy', async () => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
await
|
|
469
|
+
original.createCredentials(profile, auth);
|
|
470
|
+
makeRequest(tokenUrl);
|
|
471
|
+
await Promise.resolve();
|
|
468
472
|
|
|
469
|
-
expect(close).toHaveBeenCalled();
|
|
473
|
+
expect(server.close).toHaveBeenCalled();
|
|
470
474
|
|
|
471
475
|
connections.forEach((connection) => expect(connection.destroy).toHaveBeenCalled());
|
|
472
476
|
});
|
|
473
477
|
|
|
474
|
-
it('should
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
478
|
+
it('should close server and resolve if request.url is truthy', async () => {
|
|
479
|
+
const promise = original.createCredentials(profile, auth);
|
|
480
|
+
makeRequest(tokenUrl);
|
|
481
|
+
const result = await Promise.resolve(promise);
|
|
482
|
+
expect(result).toEqual(credentialsJSON);
|
|
483
|
+
expect(server.close).toHaveBeenCalledTimes(1);
|
|
484
|
+
});
|
|
481
485
|
|
|
482
|
-
|
|
483
|
-
|
|
486
|
+
it('should not close server if request.url is falsy', async () => {
|
|
487
|
+
original.createCredentials(profile, auth);
|
|
488
|
+
makeRequest(undefined);
|
|
489
|
+
await Promise.resolve();
|
|
484
490
|
|
|
485
|
-
expect(close).
|
|
486
|
-
expect(closedTime - before).toBeGreaterThanOrEqual(requestTime - 1);
|
|
487
|
-
expect(after - before).toBeGreaterThanOrEqual(requestTime - 1);
|
|
488
|
-
expect(result).toEqual(credentialsJSON);
|
|
491
|
+
expect(server.close).not.toHaveBeenCalled();
|
|
489
492
|
});
|
|
490
493
|
|
|
491
|
-
it('should
|
|
492
|
-
const
|
|
493
|
-
const requestTime = 200;
|
|
494
|
+
it('should re-throw a server error if error is not EADDRINUSE', () => {
|
|
495
|
+
const error = { code : 'RANDOM', message : 'random error' } as NodeJS.ErrnoException;
|
|
494
496
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
497
|
+
original.createCredentials(profile, auth);
|
|
498
|
+
expect(() => server.emit('error', error)).toThrow(error.message);
|
|
499
|
+
});
|
|
498
500
|
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
+
it('should not re-throw a server error and try to listen again in 1000 seconds if error is EADDRINUSE', () => {
|
|
502
|
+
const error = { code : 'EADDRINUSE' } as NodeJS.ErrnoException;
|
|
501
503
|
|
|
502
|
-
|
|
503
|
-
expect(
|
|
504
|
-
expect(
|
|
505
|
-
expect(
|
|
504
|
+
original.createCredentials(profile, auth);
|
|
505
|
+
expect(server.listen).toHaveBeenCalledTimes(1);
|
|
506
|
+
expect(() => server.emit('error', error)).not.toThrow();
|
|
507
|
+
expect(server.listen).toHaveBeenCalledTimes(1);
|
|
508
|
+
jest.advanceTimersByTime(1000);
|
|
509
|
+
expect(server.listen).toHaveBeenCalledTimes(2);
|
|
506
510
|
});
|
|
507
511
|
|
|
508
512
|
it('should return credentials JSON', async () => {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const result = await
|
|
513
|
+
const promise = original.createCredentials(profile, auth);
|
|
514
|
+
makeRequest(tokenUrl);
|
|
515
|
+
const result = await promise;
|
|
512
516
|
|
|
513
517
|
expect(result).toEqual(credentialsJSON);
|
|
514
518
|
});
|
|
515
519
|
});
|
|
516
520
|
|
|
521
|
+
describe('deleteCredentials', () => {
|
|
522
|
+
it('should delete credentials file if exists', () => {
|
|
523
|
+
exists = true;
|
|
524
|
+
original.deleteCredentials(profile);
|
|
525
|
+
expect(fs.rmSync).toHaveBeenCalledWith(credentialsFile);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('should not do anything if credentials file does not exist', () => {
|
|
529
|
+
exists = false;
|
|
530
|
+
original.deleteCredentials(profile);
|
|
531
|
+
expect(fs.rmSync).not.toHaveBeenCalled();
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
517
535
|
describe('checkSecrets', () => {
|
|
518
536
|
it('should return true if redirect_uri is correct', () => {
|
|
519
537
|
const result = original.checkSecrets(profile, secretsJSON, secretsFile);
|