@anmiles/google-api-wrapper 18.0.3 → 19.0.1

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.
Files changed (108) hide show
  1. package/.nycrc.json +27 -0
  2. package/CHANGELOG.md +32 -21
  3. package/README.md +1 -1
  4. package/cspell.json +22 -0
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4 -3
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/api.d.ts +5 -11
  10. package/dist/lib/api.d.ts.map +1 -1
  11. package/dist/lib/api.js +5 -6
  12. package/dist/lib/api.js.map +1 -1
  13. package/dist/lib/auth.d.ts +2 -9
  14. package/dist/lib/auth.d.ts.map +1 -1
  15. package/dist/lib/auth.js +3 -23
  16. package/dist/lib/auth.js.map +1 -1
  17. package/dist/lib/credentials/generator.d.ts +4 -0
  18. package/dist/lib/credentials/generator.d.ts.map +1 -0
  19. package/dist/lib/credentials/generator.js +60 -0
  20. package/dist/lib/credentials/generator.js.map +1 -0
  21. package/dist/lib/credentials/index.d.ts +5 -0
  22. package/dist/lib/credentials/index.d.ts.map +1 -0
  23. package/dist/lib/credentials/index.js +42 -0
  24. package/dist/lib/credentials/index.js.map +1 -0
  25. package/dist/lib/credentials/validator.d.ts +7 -0
  26. package/dist/lib/credentials/validator.d.ts.map +1 -0
  27. package/dist/lib/credentials/validator.js +20 -0
  28. package/dist/lib/credentials/validator.js.map +1 -0
  29. package/dist/lib/login.d.ts +3 -0
  30. package/dist/lib/login.d.ts.map +1 -0
  31. package/dist/lib/login.js +19 -0
  32. package/dist/lib/login.js.map +1 -0
  33. package/dist/lib/profiles.d.ts +4 -12
  34. package/dist/lib/profiles.d.ts.map +1 -1
  35. package/dist/lib/profiles.js +8 -11
  36. package/dist/lib/profiles.js.map +1 -1
  37. package/dist/lib/renderer.d.ts +7 -25
  38. package/dist/lib/renderer.d.ts.map +1 -1
  39. package/dist/lib/renderer.js +26 -23
  40. package/dist/lib/renderer.js.map +1 -1
  41. package/dist/lib/scopes.d.ts +2 -0
  42. package/dist/lib/scopes.d.ts.map +1 -0
  43. package/dist/lib/scopes.js +22 -0
  44. package/dist/lib/scopes.js.map +1 -0
  45. package/dist/lib/secrets.d.ts +2 -28
  46. package/dist/lib/secrets.d.ts.map +1 -1
  47. package/dist/lib/secrets.js +15 -115
  48. package/dist/lib/secrets.js.map +1 -1
  49. package/dist/lib/utils/paths.d.ts +7 -0
  50. package/dist/lib/utils/paths.d.ts.map +1 -0
  51. package/dist/lib/{paths.js → utils/paths.js} +6 -8
  52. package/dist/lib/utils/paths.js.map +1 -0
  53. package/dist/templates/auth.html +1 -1
  54. package/dist/templates/index.html +6 -1
  55. package/dist/templates/page.html +1 -2
  56. package/dist/templates/script.html +0 -0
  57. package/{src/templates/css.html → dist/templates/style.html} +2 -2
  58. package/dist/types/options.d.ts +2 -3
  59. package/dist/types/options.d.ts.map +1 -1
  60. package/dist/types/secrets.d.ts +1 -2
  61. package/dist/types/secrets.d.ts.map +1 -1
  62. package/eslint.config.mts +43 -0
  63. package/jest.config.js +9 -9
  64. package/package.json +40 -30
  65. package/src/index.ts +3 -2
  66. package/src/lib/__tests__/__snapshots__/renderer.test.ts.snap +273 -0
  67. package/src/lib/__tests__/__snapshots__/scopes.test.ts.snap +6 -0
  68. package/src/lib/__tests__/__snapshots__/secrets.test.ts.snap +38 -0
  69. package/src/lib/__tests__/api.test.ts +72 -74
  70. package/src/lib/__tests__/auth.test.ts +38 -114
  71. package/src/lib/__tests__/login.test.ts +71 -0
  72. package/src/lib/__tests__/profiles.test.ts +50 -93
  73. package/src/lib/__tests__/renderer.test.ts +16 -89
  74. package/src/lib/__tests__/scopes.test.ts +41 -0
  75. package/src/lib/__tests__/secrets.test.ts +47 -541
  76. package/src/lib/api.ts +19 -21
  77. package/src/lib/auth.ts +5 -25
  78. package/src/lib/credentials/__tests__/generator.test.ts +249 -0
  79. package/src/lib/credentials/__tests__/index.test.ts +213 -0
  80. package/src/lib/credentials/__tests__/validator.test.ts +43 -0
  81. package/src/lib/credentials/generator.ts +70 -0
  82. package/src/lib/credentials/index.ts +50 -0
  83. package/src/lib/credentials/validator.ts +29 -0
  84. package/src/lib/login.ts +22 -0
  85. package/src/lib/profiles.ts +9 -12
  86. package/src/lib/renderer.ts +32 -27
  87. package/src/lib/scopes.ts +18 -0
  88. package/src/lib/secrets.ts +21 -141
  89. package/src/lib/utils/paths.ts +30 -0
  90. package/src/templates/auth.html +1 -1
  91. package/src/templates/index.html +6 -1
  92. package/src/templates/page.html +1 -2
  93. package/src/templates/script.html +0 -0
  94. package/{dist/templates/css.html → src/templates/style.html} +2 -2
  95. package/src/types/options.ts +5 -7
  96. package/src/types/secrets.ts +8 -10
  97. package/tsconfig.build.json +6 -0
  98. package/tsconfig.json +0 -5
  99. package/tsconfig.test.json +1 -1
  100. package/.eslintignore +0 -2
  101. package/.eslintrc.js +0 -30
  102. package/.vscode/settings.json +0 -9
  103. package/coverage.config.js +0 -8
  104. package/dist/lib/paths.d.ts +0 -16
  105. package/dist/lib/paths.d.ts.map +0 -1
  106. package/dist/lib/paths.js.map +0 -1
  107. package/src/lib/__tests__/paths.test.ts +0 -77
  108. package/src/lib/paths.ts +0 -32
@@ -1,569 +1,75 @@
1
- import fs from 'fs';
2
- import http from 'http';
3
- import type path from 'path';
4
- import { open } from 'out-url';
5
- import type GoogleApis from 'googleapis';
6
- import logger from '@anmiles/logger';
7
- import emitter from 'event-emitter';
8
- import type renderer from '../renderer';
9
- import type paths from '../paths';
10
- import type { Secrets } from '../../types/secrets';
11
1
  import '@anmiles/prototypes';
2
+ import mockFs from 'mock-fs';
12
3
 
13
- import secrets from '../secrets';
14
-
15
- const original = jest.requireActual<{ default : typeof secrets }>('../secrets').default;
16
- jest.mock<typeof secrets>('../secrets', () => ({
17
- getScopes : jest.fn().mockImplementation(() => scopesJSON),
18
- getSecrets : jest.fn().mockImplementation(() => secretsJSON),
19
- getCredentials : jest.fn(),
20
- validateCredentials : jest.fn(),
21
- createCredentials : jest.fn().mockImplementation(() => credentialsJSON),
22
- deleteCredentials : jest.fn(),
23
- checkSecrets : jest.fn(),
24
- getScopesError : jest.fn().mockImplementation(() => scopesError),
25
- getSecretsError : jest.fn().mockImplementation(() => secretsError),
26
- }));
27
-
28
- jest.mock<Partial<typeof renderer>>('../renderer', () => ({
29
- renderAuth : jest.fn().mockImplementation(({ profile, authUrl, scope } : { profile : string; authUrl : string; scope : string[] }) => `content = profile = ${profile} authUrl = ${authUrl} scope = ${scope.join('|')}`),
30
- renderDone : jest.fn().mockImplementation(() => 'content = done'),
31
- }));
32
-
33
- jest.mock<Partial<typeof http>>('http', () => ({
34
- createServer : jest.fn().mockImplementation(() => server),
35
- }));
36
-
37
- let server: http.Server;
38
- let response: http.ServerResponse;
39
-
40
- function makeRequest(url: string | undefined): void {
41
- server.emit('request', {
42
- url,
43
- headers : {
44
- host,
4
+ import type { Secrets } from '../../types/secrets';
5
+ import { getScopes } from '../scopes';
6
+ import { getSecrets } from '../secrets';
7
+ import { getSecretsFile } from '../utils/paths';
8
+
9
+ jest.mock('../scopes');
10
+
11
+ const profile = 'username1';
12
+
13
+ const secretsFile = getSecretsFile(profile);
14
+
15
+ function createSecrets(callbackURI: string): Secrets {
16
+ return {
17
+ web: {
18
+ client_id : 'client_id.apps.googleusercontent.com',
19
+ project_id : 'project_id',
20
+ auth_uri : 'https://accounts.google.com/o/oauth2/auth',
21
+ token_uri : 'https://oauth2.googleapis.com/token',
22
+ auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
23
+ client_secret : 'client_secret',
24
+ redirect_uris : [ callbackURI ],
45
25
  },
46
- } as http.IncomingMessage, response);
26
+ };
47
27
  }
48
28
 
49
- jest.mock<Partial<typeof fs>>('fs', () => ({
50
- existsSync : jest.fn().mockImplementation(() => exists),
51
- rmSync : jest.fn(),
52
- }));
53
-
54
- jest.mock<Partial<typeof path>>('path', () => ({
55
- join : jest.fn().mockImplementation((...paths: string[]) => paths.join('/')),
56
- }));
57
-
58
- jest.mock<{ open : typeof open }>('out-url', () => ({
59
- open : jest.fn().mockImplementation((url: string) => {
60
- makeRequest(url.replace('http://localhost:6006', ''));
61
- }),
62
- }));
63
-
64
- jest.mock<Partial<typeof logger>>('@anmiles/logger', () => ({
65
- warn : jest.fn(),
66
- }));
67
-
68
- jest.mock<Partial<typeof paths>>('../paths', () => ({
69
- getScopesFile : jest.fn().mockImplementation(() => scopesFile),
70
- getSecretsFile : jest.fn().mockImplementation(() => secretsFile),
71
- getCredentialsFile : jest.fn().mockImplementation(() => credentialsFile),
72
- getTemplateFile : jest.fn().mockImplementation(() => templateFile),
73
- }));
29
+ const port = 6006;
30
+ const host = `localhost:${port}`;
74
31
 
75
- jest.useFakeTimers();
32
+ const secrets = createSecrets(`http://${host}/oauthcallback`);
33
+ const incorrectSecrets = createSecrets('wrong_redirect_uri');
76
34
 
77
- const port = 6006;
78
- const host = `localhost:${port}`;
79
- const callbackURI = `http://${host}/oauthcallback`;
35
+ const scopes = [ 'scope1', 'scope2' ];
80
36
 
81
- const profile = 'username1';
82
- const scopesFile = 'scopes.json';
83
- const secretsFile = 'secrets/username1.json';
84
- const credentialsFile = 'secrets/username1.credentials.json';
85
- const templateFile = 'templates/template.json';
86
- const wrongRedirectURI = 'wrong_redirect_uri';
87
-
88
- const scopesError = 'scopesError';
89
- const secretsError = 'secretsError';
90
-
91
- const scopesJSON: string[] = [
92
- 'https://www.googleapis.com/auth/calendar.calendars.readonly',
93
- 'https://www.googleapis.com/auth/calendar.events',
94
- ];
95
-
96
- const secretsJSON: Secrets = {
97
- web : {
98
- client_id : 'client_id.apps.googleusercontent.com',
99
- project_id : 'project_id',
100
- auth_uri : 'https://accounts.google.com/o/oauth2/auth',
101
- token_uri : 'https://oauth2.googleapis.com/token',
102
- auth_provider_x509_cert_url : 'https://www.googleapis.com/oauth2/v1/certs',
103
- client_secret : 'client_secret',
104
- redirect_uris : [ callbackURI ],
105
- },
106
- };
107
-
108
- const credentialsJSON: GoogleApis.Auth.Credentials = {
109
- access_token : 'access_token222',
110
- };
111
-
112
- let json: unknown;
113
-
114
- const code = 'code';
115
- const authUrl = 'https://authUrl';
116
- const auth = {
117
- generateAuthUrl : jest.fn().mockReturnValue(authUrl),
118
- getToken : jest.fn().mockResolvedValue({ tokens : credentialsJSON }),
119
- } as unknown as GoogleApis.Common.OAuth2Client;
120
-
121
- let exists: boolean;
122
-
123
- const getJSONSpy = jest.spyOn(fs, 'getJSON').mockReturnValue(json);
124
- const getJSONAsyncSpy = jest.spyOn(fs, 'getJSONAsync').mockResolvedValue(json);
125
- const readJSONSpy = jest.spyOn(fs, 'readJSON').mockReturnValue(json);
37
+ jest.mocked(getScopes).mockReturnValue(scopes);
126
38
 
127
39
  beforeEach(() => {
128
- getJSONSpy.mockReturnValue(json);
129
- getJSONAsyncSpy.mockResolvedValue(json);
130
- readJSONSpy.mockReturnValue(json);
40
+ mockFs({
41
+ [secretsFile]: JSON.stringify(secrets),
42
+ });
131
43
  });
132
44
 
133
- describe('src/lib/secrets', () => {
134
- describe('getScopes', () => {
135
- beforeEach(() => {
136
- json = scopesJSON;
137
- });
138
-
139
- it('should get json from scopes file', () => {
140
- original.getScopes();
141
-
142
- expect(getJSONSpy).toHaveBeenCalled();
143
- expect(getJSONSpy.mock.calls[0]?.[0]).toEqual(scopesFile);
144
- });
145
-
146
- it('should fallback to error', () => {
147
- original.getScopes();
148
-
149
- expect(getJSONSpy.mock.calls[0]?.[1]).toThrow(scopesError);
150
- });
151
-
152
- it('should return scopes', () => {
153
- const result = original.getScopes();
154
-
155
- expect(result).toEqual(scopesJSON);
156
- });
157
- });
45
+ afterAll(() => {
46
+ mockFs.restore();
47
+ });
158
48
 
49
+ describe('src/lib/secrets', () => {
159
50
  describe('getSecrets', () => {
160
- beforeEach(() => {
161
- json = secretsJSON;
162
- });
163
-
164
- it('should get json from secrets file', () => {
165
- original.getSecrets(profile);
166
-
167
- expect(getJSONSpy).toHaveBeenCalled();
168
- expect(getJSONSpy.mock.calls[0]?.[0]).toEqual(secretsFile);
169
- });
170
-
171
- it('should fallback to error', () => {
172
- original.getSecrets(profile);
173
-
174
- expect(getJSONSpy.mock.calls[0]?.[1]).toThrow(secretsError);
175
- });
176
-
177
- it('should check secrets', () => {
178
- original.getSecrets(profile);
179
-
180
- expect(secrets.checkSecrets).toHaveBeenCalledWith(profile, json, secretsFile);
181
- });
182
-
183
51
  it('should return secrets', () => {
184
- const result = original.getSecrets(profile);
185
-
186
- expect(result).toEqual(secretsJSON);
187
- });
188
- });
189
-
190
- describe('getCredentials', () => {
191
- beforeEach(() => {
192
- json = credentialsJSON;
193
- exists = false;
194
- });
195
-
196
- it('should get json from credentials file by default', async () => {
197
- await original.getCredentials(profile, auth);
198
-
199
- expect(getJSONAsyncSpy).toHaveBeenCalled();
200
- expect(getJSONAsyncSpy.mock.calls[0]?.[0]).toEqual(credentialsFile);
201
- });
202
-
203
- it('should get json from credentials file if temporariness not set', async () => {
204
- await original.getCredentials(profile, auth, { temporary : false });
205
-
206
- expect(getJSONAsyncSpy).toHaveBeenCalled();
207
- expect(getJSONAsyncSpy.mock.calls[0]?.[0]).toEqual(credentialsFile);
208
- });
209
-
210
- it('should not get json from credentials file if temporariness set', async () => {
211
- await original.getCredentials(profile, auth, { temporary : true });
212
-
213
- expect(getJSONAsyncSpy).not.toHaveBeenCalled();
214
- });
215
-
216
- it('should call createCredentials with consent in fallback if no existing credentials', async () => {
217
- exists = false;
218
-
219
- await original.getCredentials(profile, auth);
220
-
221
- expect(secrets.createCredentials).not.toHaveBeenCalled();
222
-
223
- const fallback = getJSONAsyncSpy.mock.calls[0]?.[1];
224
- const result = await fallback?.();
225
-
226
- expect(readJSONSpy).not.toHaveBeenCalled();
227
- expect(secrets.createCredentials).toHaveBeenCalledWith(profile, auth, undefined, 'consent');
228
- expect(result).toEqual(credentialsJSON);
229
- });
230
-
231
- it('should call createCredentials with consent in fallback if no existing credentials and pass temporariness', async () => {
232
- exists = false;
233
-
234
- await original.getCredentials(profile, auth, { temporary : false });
235
-
236
- expect(secrets.createCredentials).not.toHaveBeenCalled();
237
-
238
- const fallback = getJSONAsyncSpy.mock.calls[0]?.[1];
239
- const result = await fallback?.();
240
-
241
- expect(readJSONSpy).not.toHaveBeenCalled();
242
- expect(secrets.createCredentials).toHaveBeenCalledWith(profile, auth, { temporary : false }, 'consent');
243
- expect(result).toEqual(credentialsJSON);
244
- });
245
-
246
- it('should call createCredentials with consent in fallback if existing credentials do not have refresh token', async () => {
247
- exists = true;
248
-
249
- await original.getCredentials(profile, auth);
250
-
251
- expect(secrets.createCredentials).not.toHaveBeenCalled();
252
-
253
- const fallback = getJSONAsyncSpy.mock.calls[0]?.[1];
254
- const result = await fallback?.();
255
-
256
- expect(readJSONSpy).toHaveBeenCalledWith(credentialsFile);
257
- expect(secrets.createCredentials).toHaveBeenCalledWith(profile, auth, undefined, 'consent');
258
- expect(result).toEqual(credentialsJSON);
259
- });
260
-
261
- it('should call createCredentials without consent in fallback and replace refresh_token if existing credentials have refresh token', async () => {
262
- exists = true;
263
- readJSONSpy.mockReturnValueOnce({ ...credentialsJSON, refresh_token : 'refresh_token' });
264
-
265
- await original.getCredentials(profile, auth);
266
-
267
- expect(secrets.createCredentials).not.toHaveBeenCalled();
268
-
269
- const fallback = getJSONAsyncSpy.mock.calls[0]?.[1];
270
- const result = await fallback?.();
271
-
272
- expect(readJSONSpy).toHaveBeenCalledWith(credentialsFile);
273
- expect(result).toEqual({ ... credentialsJSON, refresh_token : 'refresh_token' });
274
- });
52
+ const result = getSecrets(profile);
275
53
 
276
- it('should call createCredentials without consent in fallback and leave refresh token if existing credentials have refresh token', async () => {
277
- exists = true;
278
- readJSONSpy.mockReturnValueOnce({ ...credentialsJSON, refresh_token : 'refresh_token' });
279
- jest.spyOn(secrets, 'createCredentials').mockResolvedValueOnce({ ...credentialsJSON, refresh_token : 'refresh_token_exists' });
280
-
281
- await original.getCredentials(profile, auth);
282
-
283
- const fallback = getJSONAsyncSpy.mock.calls[0]?.[1];
284
- const result = await fallback?.();
285
-
286
- expect(readJSONSpy).toHaveBeenCalledWith(credentialsFile);
287
- expect(secrets.createCredentials).toHaveBeenCalledWith(profile, auth, undefined, undefined);
288
- expect(result).toEqual({ ...credentialsJSON, refresh_token : 'refresh_token_exists' });
289
- });
290
- });
291
-
292
- describe('validateCredentials', () => {
293
- it('should return false if no access token', async () => {
294
- expect(await original.validateCredentials({})).toEqual({ isValid : false, validationError : 'Credentials does not have access_token' });
295
- });
296
-
297
- it('should return false if no refresh token', async () => {
298
- expect(await original.validateCredentials({ access_token : 'token' })).toEqual({ isValid : false, validationError : 'Credentials does not have refresh_token' });
299
- });
300
-
301
- it('should return false if no expiration date', async () => {
302
- expect(await original.validateCredentials({ access_token : 'token', refresh_token : 'token' })).toEqual({ isValid : false, validationError : 'Credentials does not have expiry_date' });
303
- });
304
-
305
- it('should return true if credentials are not more than 1 week ago', async () => {
306
- const expiryDate = new Date();
307
- expiryDate.setDate(expiryDate.getDate() - 6);
308
- expect(await original.validateCredentials({ access_token : 'token', refresh_token : 'token', expiry_date : expiryDate.getTime() })).toEqual({ isValid : true });
309
- });
310
-
311
- it('should return false if credentials are more than 1 week ago', async () => {
312
- const expiryDate = new Date();
313
- expiryDate.setDate(expiryDate.getDate() - 8);
314
- expect(await original.validateCredentials({ access_token : 'token', refresh_token : 'token', expiry_date : expiryDate.getTime() })).toEqual({ isValid : false, validationError : 'Credentials expired' });
315
- });
316
- });
317
-
318
- describe('createCredentials', () => {
319
- const tokenUrl = `/request.url?code=${code}`;
320
-
321
- const connections = [
322
- { remoteAddress : 'server', remotePort : '1001', on : jest.fn(), destroy : jest.fn() },
323
- { remoteAddress : 'server', remotePort : '1002', on : jest.fn(), destroy : jest.fn() },
324
- { remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
325
- ];
326
-
327
- let endSpy: jest.SpyInstance;
328
-
329
- beforeEach(() => {
330
- server = emitter({
331
- listen : jest.fn().mockImplementation(() => {
332
- // always simulate opening several connections once connections are meant to be listened
333
- connections.forEach((connection) => server.emit('connection', connection));
334
- }),
335
- close : jest.fn(),
336
- destroy : jest.fn(),
337
- }) as typeof server;
338
-
339
- response = emitter({
340
- end : jest.fn(),
341
- }) as typeof response;
342
-
343
- endSpy = jest.spyOn(response, 'end');
54
+ expect(result).toEqual(secrets);
344
55
  });
345
56
 
346
- afterAll(() => {
347
- endSpy.mockRestore();
348
- });
57
+ it('should throw if no secrets file', () => {
58
+ mockFs({});
349
59
 
350
- it('should generate authUrl', async () => {
351
- void original.createCredentials(profile, auth);
352
- await Promise.resolve();
60
+ const func = (): Secrets => getSecrets(profile);
353
61
 
354
- expect(auth.generateAuthUrl).toHaveBeenCalledWith({
355
- access_type : 'offline',
356
- prompt : undefined,
357
- scope : [
358
- 'https://www.googleapis.com/auth/calendar.calendars.readonly',
359
- 'https://www.googleapis.com/auth/calendar.events',
360
- ],
361
- });
62
+ expect(func).toThrowErrorMatchingSnapshot();
362
63
  });
363
64
 
364
- it('should generate authUrl and require consent if explicitly asked', async () => {
365
- void original.createCredentials(profile, auth, { temporary : true }, 'consent');
366
- await Promise.resolve();
367
-
368
- expect(auth.generateAuthUrl).toHaveBeenCalledWith({
369
- access_type : 'offline',
370
- prompt : 'consent',
371
- scope : [
372
- 'https://www.googleapis.com/auth/calendar.calendars.readonly',
373
- 'https://www.googleapis.com/auth/calendar.events',
374
- ],
65
+ it('should throw if callbackURI is incorrect', () => {
66
+ mockFs({
67
+ [secretsFile]: JSON.stringify(incorrectSecrets),
375
68
  });
376
- });
377
-
378
- it('should generate authUrl with custom scopes', async () => {
379
- void original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
380
- await Promise.resolve();
381
69
 
382
- expect(auth.generateAuthUrl).toHaveBeenCalledWith({
383
- access_type : 'offline',
384
- prompt : undefined,
385
- scope : [ 'scope1', 'scope2' ],
386
- });
387
- });
388
-
389
- it('should create server on 6006 port', async () => {
390
- void original.createCredentials(profile, auth);
391
- await Promise.resolve();
392
-
393
- expect(http.createServer).toHaveBeenCalled();
394
- expect(server.listen).toHaveBeenCalledWith(6006);
395
- });
396
-
397
- it('should open browser page and warn about it once listening', async () => {
398
- void original.createCredentials(profile, auth);
399
- await Promise.resolve();
400
-
401
- server.emit('listening');
402
-
403
- expect(open).toHaveBeenCalledWith('http://localhost:6006/');
404
- expect(logger.warn).toHaveBeenCalledWith('Please check your browser for further actions');
405
- });
406
-
407
- it('should not open browser page and warn about it until listening', async () => {
408
- void original.createCredentials(profile, auth);
409
- await Promise.resolve();
410
-
411
- expect(open).not.toHaveBeenCalled();
412
- expect(logger.warn).not.toHaveBeenCalled();
413
- });
414
-
415
- it('should show nothing on the browser page if request.url is empty', async () => {
416
- void original.createCredentials(profile, auth);
417
- makeRequest('');
418
- await Promise.resolve();
419
-
420
- expect(endSpy).toHaveBeenCalledWith('');
421
- });
422
-
423
- it('should show opening instructions if opened the home page', async () => {
424
- void original.createCredentials(profile, auth);
425
- makeRequest('/');
426
- await Promise.resolve();
427
-
428
- expect(endSpy).toHaveBeenCalledWith('content = profile = username1 authUrl = https://authUrl scope = https://www.googleapis.com/auth/calendar.calendars.readonly|https://www.googleapis.com/auth/calendar.events');
429
- });
430
-
431
- it('should ask to close webpage', async () => {
432
- void original.createCredentials(profile, auth);
433
- makeRequest(tokenUrl);
434
- await Promise.resolve();
435
-
436
- expect(endSpy).toHaveBeenCalledWith('content = done');
437
- });
438
-
439
- it('should close server and destroy all connections if request.url is truthy', async () => {
440
- void original.createCredentials(profile, auth);
441
- makeRequest(tokenUrl);
442
- await Promise.resolve();
443
-
444
- expect(server.close).toHaveBeenCalled();
445
-
446
- connections.forEach((connection) => {
447
- expect(connection.destroy).toHaveBeenCalled();
448
- });
449
- });
450
-
451
- it('should close server and resolve if request.url is truthy', async () => {
452
- const promise = original.createCredentials(profile, auth);
453
- makeRequest(tokenUrl);
454
- const result = await Promise.resolve(promise);
455
- expect(result).toEqual(credentialsJSON);
456
- expect(server.close).toHaveBeenCalledTimes(1);
457
- });
458
-
459
- it('should not close server if request.url is falsy', async () => {
460
- void original.createCredentials(profile, auth);
461
- makeRequest(undefined);
462
- await Promise.resolve();
463
-
464
- expect(server.close).not.toHaveBeenCalled();
465
- });
466
-
467
- it('should re-throw a server error if error is not EADDRINUSE', () => {
468
- const error = { code : 'RANDOM', message : 'random error' } as NodeJS.ErrnoException;
469
-
470
- void original.createCredentials(profile, auth);
471
- expect(() => server.emit('error', error)).toThrow(error.message);
472
- });
473
-
474
- it('should not re-throw a server error and try to listen again in 1000 seconds if error is EADDRINUSE', () => {
475
- const error = { code : 'EADDRINUSE' } as NodeJS.ErrnoException;
476
-
477
- void original.createCredentials(profile, auth);
478
- expect(server.listen).toHaveBeenCalledTimes(1);
479
- expect(() => server.emit('error', error)).not.toThrow();
480
- expect(server.listen).toHaveBeenCalledTimes(1);
481
- jest.advanceTimersByTime(1000);
482
- expect(server.listen).toHaveBeenCalledTimes(2);
483
- });
484
-
485
- it('should return credentials JSON', async () => {
486
- const promise = original.createCredentials(profile, auth);
487
- makeRequest(tokenUrl);
488
- const result = await promise;
489
-
490
- expect(result).toEqual(credentialsJSON);
491
- });
492
- });
493
-
494
- describe('deleteCredentials', () => {
495
- it('should delete credentials file if exists', () => {
496
- exists = true;
497
- original.deleteCredentials(profile);
498
- expect(fs.rmSync).toHaveBeenCalledWith(credentialsFile);
499
- });
500
-
501
- it('should not do anything if credentials file does not exist', () => {
502
- exists = false;
503
- original.deleteCredentials(profile);
504
- expect(fs.rmSync).not.toHaveBeenCalled();
505
- });
506
- });
507
-
508
- describe('checkSecrets', () => {
509
- it('should return true if redirect_uri is correct', () => {
510
- const result = original.checkSecrets(profile, secretsJSON, secretsFile);
511
-
512
- expect(result).toBe(true);
513
- });
514
-
515
- it('should output error if redirect_uri is incorrect', () => {
516
- const wrongSecretsJSON = { ...secretsJSON };
517
- wrongSecretsJSON.web.redirect_uris[0] = wrongRedirectURI;
518
- const func = (): true => original.checkSecrets(profile, wrongSecretsJSON, secretsFile);
519
-
520
- expect(func).toThrow('Error in credentials file: redirect URI should be http://localhost:6006/oauthcallback.\nsecretsError');
521
- });
522
- });
523
-
524
- describe('getScopesError', () => {
525
- it('should return error message with instructions', () => {
526
- const result = original.getScopesError(scopesFile);
527
- expect(result).toEqual(`File ${scopesFile} not found!\n\
528
- This application had to have pre-defined file ${scopesFile} that will declare needed scopes`);
529
- });
530
- });
70
+ const func = (): Secrets => getSecrets(profile);
531
71
 
532
- describe('getSecretsError', () => {
533
- it('should return error message with instructions', () => {
534
- const result = original.getSecretsError(profile, secretsFile);
535
- expect(result).toEqual(`File ${secretsFile} not found!\n\
536
- Here is how to obtain it:\n\
537
- Go to https://console.cloud.google.com/projectcreate\n\
538
- Choose project name\n\
539
- Click "CREATE" and wait for project to become created\n\
540
- Go to https://console.cloud.google.com/apis/dashboard\n\
541
- Select just created project in the top left dropdown list\n\
542
- Click "ENABLE APIS AND SERVICES"\n\
543
- Click API you need\n\
544
- Click "ENABLE" and wait for API to become enabled\n\
545
- Click "Credentials" tab on the left sidebar\n\
546
- Click "CONFIGURE CONSENT SCREEN" on the right\n\
547
- Choose "External"\n\
548
- Click "CREATE"\n\
549
- Choose app name, i.e. "NodeJS"\n\
550
- Specify your email as user support email and as developer contact information on the very bottom\n\
551
- Click "Save and continue"\n\
552
- Click "Add or remove scopes"\n\
553
- Add scopes: https://www.googleapis.com/auth/calendar.calendars.readonly,https://www.googleapis.com/auth/calendar.events\n\
554
- Click "Save and continue"\n\
555
- Click "Add users"\n\
556
- Add your email\n\
557
- Click "Save and continue"\n\
558
- Click "Back to dashboard" on the very bottom\n\
559
- Click "Credentials" on the left sidebar\n\
560
- Click "CREATE CREDENTIALS" and choose "OAuth client ID"\n\
561
- Select application type "Web application"\n\
562
- Specify app name, i.e. "NodeJS"\n\
563
- Add authorized redirect URI: http://localhost:6006/oauthcallback\n\
564
- Click "CREATE"\n\
565
- Click "DOWNLOAD JSON" and download credentials to ./secrets/${profile}.json\n\
566
- Then start this script again`);
72
+ expect(func).toThrowErrorMatchingSnapshot();
567
73
  });
568
74
  });
569
75
  });