zapier-platform-cli 17.3.0 → 17.4.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.
Files changed (30) hide show
  1. package/oclif.manifest.json +16 -4
  2. package/package.json +1 -1
  3. package/scaffold/resource.template.ts +30 -9
  4. package/src/generators/index.js +70 -68
  5. package/src/generators/templates/README.template.md +10 -0
  6. package/src/generators/templates/authTests/basic.test.ts +42 -0
  7. package/src/generators/templates/authTests/custom.test.ts +34 -0
  8. package/src/generators/templates/authTests/digest.test.ts +43 -0
  9. package/src/generators/templates/authTests/oauth1.test.ts +63 -0
  10. package/src/generators/templates/authTests/oauth2.test.ts +115 -0
  11. package/src/generators/templates/authTests/session.test.ts +36 -0
  12. package/src/generators/templates/index.template.ts +11 -14
  13. package/src/generators/templates/tsconfig.template.json +18 -0
  14. package/src/oclif/commands/init.js +9 -2
  15. package/src/oclif/commands/versions.js +0 -24
  16. package/src/utils/auth-files-codegen.js +141 -69
  17. package/src/utils/build.js +62 -53
  18. package/src/utils/codegen.js +24 -4
  19. package/src/utils/files.js +12 -2
  20. package/src/utils/zapierwrapper.js +1 -1
  21. package/src/generators/templates/index-esm.template.ts +0 -24
  22. package/src/generators/templates/typescript/README.md +0 -3
  23. package/src/generators/templates/typescript/src/authentication.ts +0 -48
  24. package/src/generators/templates/typescript/src/constants.ts +0 -3
  25. package/src/generators/templates/typescript/src/creates/movie.ts +0 -43
  26. package/src/generators/templates/typescript/src/middleware.ts +0 -11
  27. package/src/generators/templates/typescript/src/test/creates.test.ts +0 -21
  28. package/src/generators/templates/typescript/src/test/triggers.test.ts +0 -25
  29. package/src/generators/templates/typescript/src/triggers/movie.ts +0 -29
  30. package/src/generators/templates/typescript/tsconfig.json +0 -17
@@ -402,7 +402,8 @@
402
402
  "examples": [
403
403
  "zapier init myapp",
404
404
  "zapier init ./path/myapp --template oauth2",
405
- "zapier init ./path/myapp --template minimal --module esm"
405
+ "zapier init ./path/myapp --template minimal --module esm",
406
+ "zapier init ./path/myapp --template oauth2 --language typescript"
406
407
  ],
407
408
  "flags": {
408
409
  "template": {
@@ -423,8 +424,7 @@
423
424
  "oauth2",
424
425
  "openai",
425
426
  "search-or-create",
426
- "session-auth",
427
- "typescript"
427
+ "session-auth"
428
428
  ],
429
429
  "type": "option"
430
430
  },
@@ -440,6 +440,18 @@
440
440
  ],
441
441
  "type": "option"
442
442
  },
443
+ "language": {
444
+ "char": "l",
445
+ "description": "Choose the language to use for your new integration. Defaults to JavaScript.",
446
+ "name": "language",
447
+ "hasDynamicHelp": false,
448
+ "multiple": false,
449
+ "options": [
450
+ "javascript",
451
+ "typescript"
452
+ ],
453
+ "type": "option"
454
+ },
443
455
  "debug": {
444
456
  "char": "d",
445
457
  "description": "Show extra debugging output.",
@@ -2493,5 +2505,5 @@
2493
2505
  ]
2494
2506
  }
2495
2507
  },
2496
- "version": "17.3.0"
2508
+ "version": "17.4.0"
2497
2509
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-cli",
3
- "version": "17.3.0",
3
+ "version": "17.4.0",
4
4
  "description": "The CLI for managing integrations in Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -1,18 +1,34 @@
1
- import type { PerformFunction, Resource } from 'zapier-platform-core';
1
+ import {
2
+ defineInputFields,
3
+ type CreatePerform,
4
+ type InferInputData,
5
+ type PollingTriggerPerform,
6
+ type Resource,
7
+ type SearchPerform,
8
+ } from 'zapier-platform-core';
2
9
 
3
10
  // get a list of <%= LOWER_NOUN %>s
4
- const performList: PerformFunction = async (z, bundle) => {
11
+ const performList = (async (z, bundle) => {
5
12
  const response = await z.request({
6
13
  url: 'https://jsonplaceholder.typicode.com/posts',
7
14
  params: {
8
15
  order_by: 'id desc',
16
+ tag: bundle.inputData.tagName,
9
17
  },
10
18
  });
11
19
  return response.data;
12
- };
20
+ }) satisfies PollingTriggerPerform;
21
+
22
+ const searchInputFields = defineInputFields([
23
+ {
24
+ key: 'name',
25
+ required: true,
26
+ helpText: 'Find the <%= NOUN %> with this name.',
27
+ },
28
+ ]);
13
29
 
14
30
  // find a particular <%= LOWER_NOUN %> by name (or other search criteria)
15
- const performSearch: PerformFunction = async (z, bundle) => {
31
+ const performSearch = (async (z, bundle) => {
16
32
  const response = await z.request({
17
33
  url: 'https://jsonplaceholder.typicode.com/posts',
18
34
  params: {
@@ -20,10 +36,15 @@ const performSearch: PerformFunction = async (z, bundle) => {
20
36
  },
21
37
  });
22
38
  return response.data;
23
- };
39
+ }) satisfies SearchPerform<InferInputData<typeof searchInputFields>>;
40
+
41
+ const createInputFields = defineInputFields([
42
+ { key: 'name', required: true },
43
+ { key: 'fave_meal', label: 'Favorite Meal', required: false },
44
+ ]);
24
45
 
25
46
  // creates a new <%= LOWER_NOUN %>
26
- const performCreate: PerformFunction = async (z, bundle) => {
47
+ const performCreate = (async (z, bundle) => {
27
48
  const response = await z.request({
28
49
  method: 'POST',
29
50
  url: 'https://jsonplaceholder.typicode.com/posts',
@@ -32,7 +53,7 @@ const performCreate: PerformFunction = async (z, bundle) => {
32
53
  },
33
54
  });
34
55
  return response.data;
35
- };
56
+ }) satisfies CreatePerform<InferInputData<typeof createInputFields>>;
36
57
 
37
58
  export default {
38
59
  // see here for a full list of available properties:
@@ -78,7 +99,7 @@ export default {
78
99
  description: 'Finds a <%= LOWER_NOUN %> give.',
79
100
  },
80
101
  operation: {
81
- inputFields: [{ key: 'name', required: true }],
102
+ inputFields: searchInputFields,
82
103
  perform: performSearch,
83
104
  },
84
105
  },
@@ -89,7 +110,7 @@ export default {
89
110
  description: 'Creates a new <%= LOWER_NOUN %>.',
90
111
  },
91
112
  operation: {
92
- inputFields: [{ key: 'name', required: true }],
113
+ inputFields: createInputFields,
93
114
  perform: performCreate,
94
115
  },
95
116
  },
@@ -67,19 +67,7 @@ const writeGenericPackageJson = (gen, packageJsonExtension) => {
67
67
  );
68
68
  };
69
69
 
70
- const writeTypeScriptPackageJson = (gen, packageJsonExtension) => {
71
- const moduleExtension =
72
- gen.options.module === 'esm'
73
- ? {
74
- exports: './dist/index.js',
75
- type: 'module',
76
- }
77
- : {
78
- main: 'index.js',
79
- };
80
-
81
- const fullExtension = merge(moduleExtension, packageJsonExtension);
82
-
70
+ const writeGenericTypeScriptPackageJson = (gen, packageJsonExtension) => {
83
71
  gen.fs.writeJSON(
84
72
  gen.destinationPath('package.json'),
85
73
  merge(
@@ -88,17 +76,24 @@ const writeTypeScriptPackageJson = (gen, packageJsonExtension) => {
88
76
  version: '1.0.0',
89
77
  description: '',
90
78
  scripts: {
91
- test: 'vitest',
79
+ test: 'npm run build && vitest --run',
80
+ clean: 'rimraf ./dist ./build',
81
+ build: 'npm run clean && tsc',
82
+ '_zapier-build': 'npm run build',
92
83
  },
93
84
  dependencies: {
94
85
  [PLATFORM_PACKAGE]: PACKAGE_VERSION,
95
86
  },
96
87
  devDependencies: {
88
+ rimraf: '^5.0.10',
89
+ typescript: '5.6.2',
97
90
  vitest: '^2.1.2',
98
91
  },
99
92
  private: true,
93
+ exports: './dist/index.js',
94
+ type: 'module',
100
95
  },
101
- fullExtension,
96
+ packageJsonExtension,
102
97
  ),
103
98
  );
104
99
  };
@@ -115,21 +110,12 @@ const writeGenericIndex = (gen, hasAuth) => {
115
110
  );
116
111
  };
117
112
 
118
- const writeTypeScriptIndex = (gen) => {
119
- const templatePath =
120
- gen.options.module === 'esm'
121
- ? 'index-esm.template.ts'
122
- : 'index.template.ts';
113
+ const writeGenericTypescriptIndex = (gen) => {
123
114
  gen.fs.copyTpl(
124
- gen.templatePath(templatePath),
115
+ gen.templatePath('index.template.ts'),
125
116
  gen.destinationPath('src/index.ts'),
117
+ { corePackageName: PLATFORM_PACKAGE },
126
118
  );
127
-
128
- // create root directory index.js if it's commonjs
129
- if (gen.options.module === 'commonjs') {
130
- const content = `module.exports = require('./dist').default;`;
131
- gen.fs.write(gen.destinationPath('index.js'), content);
132
- }
133
119
  };
134
120
 
135
121
  const authTypes = {
@@ -143,15 +129,23 @@ const authTypes = {
143
129
 
144
130
  const writeGenericAuth = (gen) => {
145
131
  const authType = authTypes[gen.options.template];
146
- const content = authFilesCodegen[authType]();
147
- gen.fs.write(gen.destinationPath('authentication.js'), content);
132
+ const content = authFilesCodegen[authType](gen.options.language);
133
+ const destPath =
134
+ gen.options.language === 'typescript'
135
+ ? 'src/authentication.ts'
136
+ : 'authentication.js';
137
+ gen.fs.write(gen.destinationPath(destPath), content);
148
138
  };
149
139
 
150
140
  const writeGenericAuthTest = (gen) => {
151
141
  const authType = authTypes[gen.options.template];
142
+ const fileExtension = gen.options.language === 'typescript' ? 'ts' : 'js';
143
+ const destPath = gen.options.language === 'typescript' ? 'src/test' : 'test';
152
144
  gen.fs.copyTpl(
153
- gen.templatePath(`authTests/${authType || 'generic'}.test.js`),
154
- gen.destinationPath('test/authentication.test.js'),
145
+ gen.templatePath(
146
+ `authTests/${authType || 'generic'}.test.${fileExtension}`,
147
+ ),
148
+ gen.destinationPath(`${destPath}/authentication.test.${fileExtension}`),
155
149
  );
156
150
  };
157
151
 
@@ -166,8 +160,17 @@ const writeGenericTest = (gen) => {
166
160
  const writeForAuthTemplate = (gen) => {
167
161
  writeGitignore(gen);
168
162
  writeGenericReadme(gen);
169
- writeGenericPackageJson(gen);
170
- writeGenericIndex(gen, true);
163
+ if (gen.options.language === 'typescript') {
164
+ writeGenericTypescriptIndex(gen);
165
+ writeGenericTypeScriptPackageJson(gen);
166
+ gen.fs.copyTpl(
167
+ gen.templatePath('tsconfig.template.json'),
168
+ gen.destinationPath('tsconfig.json'),
169
+ );
170
+ } else {
171
+ writeGenericIndex(gen, true);
172
+ writeGenericPackageJson(gen);
173
+ }
171
174
  writeGenericAuth(gen);
172
175
  writeGenericAuthTest(gen);
173
176
  };
@@ -205,36 +208,6 @@ const writeForStandaloneTemplate = (gen) => {
205
208
  );
206
209
  };
207
210
 
208
- const writeForStandaloneTypeScriptTemplate = (gen) => {
209
- writeGitignore(gen);
210
- writeGenericReadme(gen);
211
- appendReadme(gen);
212
-
213
- const packageJsonExtension = {
214
- typescript: {
215
- scripts: {
216
- test: 'vitest --run',
217
- clean: 'rimraf ./dist ./build',
218
- build: 'npm run clean && tsc',
219
- '_zapier-build': 'npm run build',
220
- },
221
- devDependencies: {
222
- rimraf: '^5.0.10',
223
- typescript: '5.6.2',
224
- vitest: '^2.1.2',
225
- },
226
- },
227
- }[gen.options.template];
228
-
229
- writeTypeScriptPackageJson(gen, packageJsonExtension);
230
-
231
- gen.fs.copy(
232
- gen.templatePath(gen.options.template, '**', '*.{js,json,ts}'),
233
- gen.destinationPath(),
234
- );
235
- writeTypeScriptIndex(gen);
236
- };
237
-
238
211
  const TEMPLATE_ROUTES = {
239
212
  'basic-auth': writeForAuthTemplate,
240
213
  callback: writeForStandaloneTemplate,
@@ -248,10 +221,19 @@ const TEMPLATE_ROUTES = {
248
221
  openai: writeForStandaloneTemplate,
249
222
  'search-or-create': writeForStandaloneTemplate,
250
223
  'session-auth': writeForAuthTemplate,
251
- typescript: writeForStandaloneTypeScriptTemplate,
252
224
  };
253
225
 
254
- const ESM_SUPPORTED_TEMPLATES = ['minimal', 'typescript'];
226
+ const ESM_SUPPORTED_TEMPLATES = ['minimal'];
227
+
228
+ // Which templates can be used with the --language typescript flag
229
+ const TS_SUPPORTED_TEMPLATES = [
230
+ 'basic-auth',
231
+ 'custom-auth',
232
+ 'digest-auth',
233
+ 'oauth1-trello',
234
+ 'oauth2',
235
+ 'session-auth',
236
+ ];
255
237
 
256
238
  const TEMPLATE_CHOICES = Object.keys(TEMPLATE_ROUTES);
257
239
 
@@ -260,7 +242,7 @@ class ProjectGenerator extends Generator {
260
242
  this.sourceRoot(path.resolve(__dirname, 'templates'));
261
243
  this.destinationRoot(path.resolve(this.options.path));
262
244
 
263
- const jsFilter = filter(['*.js', '*.json'], { restore: true });
245
+ const jsFilter = filter(['*.js', '*.json', '*.ts'], { restore: true });
264
246
  this.queueTransformStream([
265
247
  jsFilter,
266
248
  prettier({ singleQuote: true }),
@@ -298,12 +280,32 @@ class ProjectGenerator extends Generator {
298
280
  this.options.module = this.answers.module;
299
281
  }
300
282
 
283
+ if (this.options.language) {
284
+ if (this.options.language === 'typescript') {
285
+ // check if the template supports typescript
286
+ if (!TS_SUPPORTED_TEMPLATES.includes(this.options.template)) {
287
+ throw new Error(
288
+ 'Typescript is not supported for this template, please use a different template or set the language to javascript. Supported templates: ' +
289
+ TS_SUPPORTED_TEMPLATES.join(', '),
290
+ );
291
+ }
292
+ // if they try to combine typescript with commonjs, throw an error
293
+ if (this.options.module === 'commonjs') {
294
+ throw new Error('Typescript is not supported for commonjs');
295
+ } // esm is supported for typescript templates
296
+ }
297
+ } else {
298
+ // default to javascript for the language if it's not set
299
+ this.options.language = 'javascript';
300
+ }
301
+
301
302
  if (
302
303
  !ESM_SUPPORTED_TEMPLATES.includes(this.options.template) &&
303
- this.options.module === 'esm'
304
+ this.options.module === 'esm' &&
305
+ this.options.language === 'javascript'
304
306
  ) {
305
307
  throw new Error(
306
- 'ESM is not supported for this template, please use a different template or set the module to commonjs',
308
+ 'ESM is not supported for this template, please use a different template, set the module to commonjs, or try setting the language to Typescript',
307
309
  );
308
310
  }
309
311
  }
@@ -21,4 +21,14 @@ zapier link
21
21
  zapier push
22
22
  ```
23
23
 
24
+ Then, to add more features, you can use the `zapier scaffold` command, for example:
25
+
26
+ ```bash
27
+ # Add a trigger
28
+ zapier scaffold trigger contact
29
+
30
+ # Add an action
31
+ zapier scaffold create contact
32
+ ```
33
+
24
34
  Find out more on the latest docs: https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md.
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import zapier from 'zapier-platform-core';
3
+
4
+ import App from '../index.js';
5
+ const appTester = zapier.createAppTester(App);
6
+
7
+ describe('basic auth', () => {
8
+ it('automatically has Authorize Header add', async () => {
9
+ const bundle = {
10
+ authData: {
11
+ username: 'user',
12
+ password: 'secret',
13
+ },
14
+ };
15
+
16
+ const response = await appTester(App.authentication.test, bundle);
17
+
18
+ expect(response.status).toBe(200);
19
+ expect(response.request.headers.Authorization).toBe(
20
+ 'Basic dXNlcjpzZWNyZXQ='
21
+ );
22
+ });
23
+
24
+ it('fails on bad auth', async () => {
25
+ const bundle = {
26
+ authData: {
27
+ username: 'user',
28
+ password: 'badpwd',
29
+ },
30
+ };
31
+
32
+ try {
33
+ await appTester(App.authentication.test, bundle);
34
+ } catch (err) {
35
+ expect(err.message).toContain(
36
+ 'The username and/or password you supplied is incorrect'
37
+ );
38
+ return;
39
+ }
40
+ throw new Error('appTester should have thrown');
41
+ });
42
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import zapier from 'zapier-platform-core';
3
+
4
+ import App from '../index.js';
5
+ const appTester = zapier.createAppTester(App);
6
+
7
+ describe('custom auth', () => {
8
+ it('passes authentication and returns json', async () => {
9
+ const bundle = {
10
+ authData: {
11
+ apiKey: 'secret',
12
+ },
13
+ };
14
+
15
+ const response = await appTester(App.authentication.test, bundle);
16
+ expect(response.data).toHaveProperty('username');
17
+ });
18
+
19
+ it('fails on bad auth', async () => {
20
+ const bundle = {
21
+ authData: {
22
+ apiKey: 'bad',
23
+ },
24
+ };
25
+
26
+ try {
27
+ await appTester(App.authentication.test, bundle);
28
+ } catch (error) {
29
+ expect(error.message).toContain('The API Key you supplied is incorrect');
30
+ return;
31
+ }
32
+ throw new Error('appTester should have thrown');
33
+ });
34
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import zapier from 'zapier-platform-core';
3
+
4
+ import App from '../index.js';
5
+ const appTester = zapier.createAppTester(App);
6
+
7
+ describe('digest auth', () => {
8
+ it('correctly authenticates', async () => {
9
+ // Try changing the values of username or password to see how the test method behaves
10
+ const bundle = {
11
+ authData: {
12
+ username: 'myuser',
13
+ password: 'mypass',
14
+ },
15
+ };
16
+
17
+ const response = await appTester(App.authentication.test, bundle);
18
+
19
+ expect(response.status).toBe(200);
20
+ expect(response.data.authorized).toBe(true);
21
+ expect(response.data.user).toBe('myuser');
22
+ });
23
+
24
+ it('fails on bad auth', async () => {
25
+ // Try changing the values of username or password to see how the test method behaves
26
+ const bundle = {
27
+ authData: {
28
+ username: 'user',
29
+ password: 'badpwd',
30
+ },
31
+ };
32
+
33
+ try {
34
+ await appTester(App.authentication.test, bundle);
35
+ } catch (err) {
36
+ expect(err.message).toContain(
37
+ 'The username and/or password you supplied is incorrect'
38
+ );
39
+ return;
40
+ }
41
+ throw new Error('appTester should have thrown');
42
+ });
43
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it, beforeAll } from 'vitest';
2
+ import zapier from 'zapier-platform-core';
3
+
4
+ import App from '../index.js';
5
+ const appTester = zapier.createAppTester(App);
6
+
7
+ // Only defining the env vars here so the tests out of the box.
8
+ // You should create a `.env` file and populate it with your actual Trello
9
+ // client ID and secret (called "API Key" and "API Secret" by Trello), which you
10
+ // can find on https://trello.com/app-key.
11
+ // The `.env` file should look like
12
+ /*
13
+ CLIENT_ID=<trello_api_key>
14
+ CLIENT_SECRET=<trello_api_secret>
15
+ */
16
+ // then you can delete the following 2 lines
17
+ process.env.CLIENT_ID = process.env.CLIENT_ID || '<trello_api_key>';
18
+ process.env.CLIENT_SECRET = process.env.CLIENT_SECRET || '<trello_api_secret>';
19
+
20
+ describe('oauth1 app', () => {
21
+ beforeAll(() => {
22
+ // It's a good idea to store your Client ID and Secret in the environment rather than in code.
23
+ if (!(process.env.CLIENT_ID && process.env.CLIENT_SECRET)) {
24
+ throw new Error(
25
+ `Before running the tests, make sure CLIENT_ID and CLIENT_SECRET are available in the environment.`
26
+ );
27
+ }
28
+ });
29
+
30
+ it('fetch a request token', async () => {
31
+ const bundle = {
32
+ inputData: {
33
+ // You should add your redirect URI to https://trello.com/app-key
34
+ redirect_uri: 'https://zapier.com',
35
+ },
36
+ };
37
+ const tokens = await appTester(
38
+ App.authentication.oauth1Config.getRequestToken,
39
+ bundle
40
+ );
41
+ expect(tokens).toHaveProperty('oauth_token');
42
+ expect(tokens).toHaveProperty('oauth_token_secret');
43
+ });
44
+
45
+ it('generates an authorize URL', async () => {
46
+ const bundle = {
47
+ // In production, these will be generated by Zapier and set automatically
48
+ inputData: {
49
+ oauth_token: '4444',
50
+ redirect_uri: 'https://zapier.com/',
51
+ },
52
+ };
53
+
54
+ const authorizeUrl = await appTester(
55
+ App.authentication.oauth1Config.authorizeUrl,
56
+ bundle
57
+ );
58
+
59
+ expect(authorizeUrl).toBe(
60
+ 'https://trello.com/1/OAuthAuthorizeToken?oauth_token=4444&name=Zapier%2FTrello%20OAuth1%20Test'
61
+ );
62
+ });
63
+ });
@@ -0,0 +1,115 @@
1
+ import { describe, expect, it, beforeAll } from 'vitest';
2
+ import zapier from 'zapier-platform-core';
3
+
4
+ import App from '../index.js';
5
+ const appTester = zapier.createAppTester(App);
6
+
7
+ // Only defining the env vars here so the tests out of the box.
8
+ // You should create a `.env` file and populate it with the necessarily configuration
9
+ // it should look like:
10
+ /*
11
+ CLIENT_ID=1234
12
+ CLIENT_SECRET=asdf
13
+ */
14
+ // then you can delete the following 2 lines
15
+ process.env.CLIENT_ID = process.env.CLIENT_ID || '1234';
16
+ process.env.CLIENT_SECRET = process.env.CLIENT_SECRET || 'asdf';
17
+
18
+ describe('authentication', () => {
19
+ beforeAll(() => {
20
+ // It's a good idea to store your Client ID and Secret in the environment rather than in code.
21
+ if (!(process.env.CLIENT_ID && process.env.CLIENT_SECRET)) {
22
+ throw new Error(
23
+ `Before running the tests, make sure CLIENT_ID and CLIENT_SECRET are available in the environment.`,
24
+ );
25
+ }
26
+ });
27
+
28
+ it('generates an authorize URL', async () => {
29
+ const bundle = {
30
+ // In production, these will be generated by Zapier and set automatically
31
+ inputData: {
32
+ state: '4444',
33
+ redirect_uri: 'https://zapier.com/',
34
+ },
35
+ environment: {
36
+ CLIENT_ID: process.env.CLIENT_ID,
37
+ CLIENT_SECRET: process.env.CLIENT_SECRET,
38
+ },
39
+ };
40
+
41
+ const authorizeUrl = await appTester(
42
+ App.authentication.oauth2Config.authorizeUrl,
43
+ bundle,
44
+ );
45
+
46
+ expect(authorizeUrl).toBe(
47
+ 'https://auth-json-server.zapier-staging.com/oauth/authorize?client_id=1234&state=4444&redirect_uri=https%3A%2F%2Fzapier.com%2F&response_type=code',
48
+ );
49
+ });
50
+
51
+ it('can fetch an access token', async () => {
52
+ const bundle = {
53
+ inputData: {
54
+ // In production, Zapier passes along whatever code your API set in the query params when it redirects
55
+ // the user's browser to the `redirect_uri`
56
+ code: 'one_time_code',
57
+ },
58
+ environment: {
59
+ CLIENT_ID: process.env.CLIENT_ID,
60
+ CLIENT_SECRET: process.env.CLIENT_SECRET,
61
+ },
62
+ cleanedRequest: {
63
+ querystring: {
64
+ accountDomain: 'test-account',
65
+ code: 'one_time_code',
66
+ },
67
+ },
68
+ rawRequest: {
69
+ querystring: '?accountDomain=test-account&code=one_time_code',
70
+ },
71
+ };
72
+
73
+ const result = await appTester(
74
+ App.authentication.oauth2Config.getAccessToken,
75
+ bundle,
76
+ );
77
+
78
+ expect(result.access_token).toBe('a_token');
79
+ expect(result.refresh_token).toBe('a_refresh_token');
80
+ });
81
+
82
+ it('can refresh the access token', async () => {
83
+ const bundle = {
84
+ // In production, Zapier provides these. For testing, we have hard-coded them.
85
+ // When writing tests for your own app, you should consider exporting them and doing process.env.MY_ACCESS_TOKEN
86
+ authData: {
87
+ access_token: 'a_token',
88
+ refresh_token: 'a_refresh_token',
89
+ },
90
+ environment: {
91
+ CLIENT_ID: process.env.CLIENT_ID,
92
+ CLIENT_SECRET: process.env.CLIENT_SECRET,
93
+ },
94
+ };
95
+
96
+ const result = await appTester(
97
+ App.authentication.oauth2Config.refreshAccessToken,
98
+ bundle,
99
+ );
100
+ expect(result.access_token).toBe('a_token');
101
+ });
102
+
103
+ it('includes the access token in future requests', async () => {
104
+ const bundle = {
105
+ authData: {
106
+ access_token: 'a_token',
107
+ refresh_token: 'a_refresh_token',
108
+ },
109
+ };
110
+
111
+ const response = await appTester(App.authentication.test, bundle);
112
+ expect(response.data).toHaveProperty('username');
113
+ expect(response.data.username).toBe('Bret');
114
+ });
115
+ });