zapier-platform-cli 17.3.1 → 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.
- package/oclif.manifest.json +16 -4
- package/package.json +1 -1
- package/scaffold/resource.template.ts +30 -9
- package/src/generators/index.js +70 -68
- package/src/generators/templates/README.template.md +10 -0
- package/src/generators/templates/authTests/basic.test.ts +42 -0
- package/src/generators/templates/authTests/custom.test.ts +34 -0
- package/src/generators/templates/authTests/digest.test.ts +43 -0
- package/src/generators/templates/authTests/oauth1.test.ts +63 -0
- package/src/generators/templates/authTests/oauth2.test.ts +115 -0
- package/src/generators/templates/authTests/session.test.ts +36 -0
- package/src/generators/templates/index.template.ts +11 -14
- package/src/generators/templates/tsconfig.template.json +18 -0
- package/src/oclif/commands/init.js +9 -2
- package/src/oclif/commands/versions.js +0 -24
- package/src/utils/auth-files-codegen.js +141 -69
- package/src/utils/build.js +49 -53
- package/src/utils/codegen.js +24 -4
- package/src/utils/files.js +12 -2
- package/src/generators/templates/index-esm.template.ts +0 -24
- package/src/generators/templates/typescript/README.md +0 -3
- package/src/generators/templates/typescript/src/authentication.ts +0 -48
- package/src/generators/templates/typescript/src/constants.ts +0 -3
- package/src/generators/templates/typescript/src/creates/movie.ts +0 -43
- package/src/generators/templates/typescript/src/middleware.ts +0 -11
- package/src/generators/templates/typescript/src/test/creates.test.ts +0 -21
- package/src/generators/templates/typescript/src/test/triggers.test.ts +0 -25
- package/src/generators/templates/typescript/src/triggers/movie.ts +0 -29
- package/src/generators/templates/typescript/tsconfig.json +0 -17
package/oclif.manifest.json
CHANGED
|
@@ -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.
|
|
2508
|
+
"version": "17.4.0"
|
|
2497
2509
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
113
|
+
inputFields: createInputFields,
|
|
93
114
|
perform: performCreate,
|
|
94
115
|
},
|
|
95
116
|
},
|
package/src/generators/index.js
CHANGED
|
@@ -67,19 +67,7 @@ const writeGenericPackageJson = (gen, packageJsonExtension) => {
|
|
|
67
67
|
);
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const
|
|
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
|
-
|
|
96
|
+
packageJsonExtension,
|
|
102
97
|
),
|
|
103
98
|
);
|
|
104
99
|
};
|
|
@@ -115,21 +110,12 @@ const writeGenericIndex = (gen, hasAuth) => {
|
|
|
115
110
|
);
|
|
116
111
|
};
|
|
117
112
|
|
|
118
|
-
const
|
|
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(
|
|
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
|
-
|
|
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(
|
|
154
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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'
|
|
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
|
|
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
|
+
});
|