Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Google App Script (UrlFetchApp) #449

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -11,40 +11,41 @@ This module converts a [Postman SDK](https://github.com/postmanlabs/postman-coll

Every code generator has two identifiers: `language` and `variant`.
* `language` of a code generator is the programming language in which the code snippet is generated.
* `variant` of a code generator is the methodology or the underlying library used by the language to send requests.
List of supported code generators:
* `variant` of a code generator is the methodology or the underlying library used by the language to send requests.

List of supported code generators:

| Language | Variant |
|-----------|---------------|
| C | libcurl |
| C# | RestSharp |
| cURL | cURL |
| Dart | http |
| Go | Native |
| HTTP | HTTP |
| C# | RestSharp |
| cURL | cURL |
| Dart | http |
| Go | Native |
| HTTP | HTTP |
| Java | OkHttp |
| Java | Unirest |
| JavaScript | Fetch |
| JavaScript | jQuery |
| JavaScript | Fetch |
| JavaScript | jQuery |
| JavaScript | UrlFetchApp |
| JavaScript | XHR |
| NodeJs | Axios |
| NodeJs | Axios |
| NodeJs | Native |
| NodeJs | Request |
| NodeJs | Unirest |
| Objective-C| NSURLSession|
| OCaml | Cohttp |
| OCaml | Cohttp |
|PHP | cURL |
|PHP | pecl_http |
|PHP | HTTP_Request2 |
| PowerShell | RestMethod |
| PowerShell | RestMethod |
| Python | http.client |
| Python | Requests |
| Ruby | Net:HTTP |
| Shell | Httpie |
| Shell | wget |
| Swift | URLSession |
## Table of contents
| Swift | URLSession |
## Table of contents

1. [Getting Started](#getting-started)
2. [Prerequisite](#prerequisite)
@@ -72,11 +73,11 @@ To run any of the postman-code-generators, ensure that you have NodeJS >= v8. A

## Usage

### Using postman-code-generators as a Library
### Using postman-code-generators as a Library
There are three functions that are exposed in postman-code-generators: getLanguageList, getOptions, and convert.

#### getLanguageList
This function returns a list of supported code generators.
This function returns a list of supported code generators.

##### Example:
```js
@@ -105,7 +106,7 @@ var codegen = require('postman-code-generators'), // require postman-code-genera
// ]
```

#### getOptions
#### getOptions

This function takes in three parameters and returns a callback with error and supported options of that code generator.

@@ -132,7 +133,7 @@ var codegen = require('postman-code-generators'), // require postman-code-genera
}
console.log(options);
});
// output:
// output:
// [
// {
// name: 'Set indentation count',
@@ -153,7 +154,7 @@ var codegen = require('postman-code-generators'), // require postman-code-genera
// ];
```

#### convert
#### convert
This function takes in five parameters and returns a callback with error and generated code snippet
* `language` - lang key from the language list returned from getLanguageList function
* `variant` - variant key provided by getLanguageList function
@@ -165,7 +166,7 @@ This function takes in five parameters and returns a callback with error and gen
```js
var codegen = require('postman-code-generators'), // require postman-code-generators in your project
sdk = require('postman-collection'), // require postman-collection in your project
request = new sdk.Request('https://www.google.com'), //using postman sdk to create request
request = new sdk.Request('https://www.google.com'), //using postman sdk to create request
language = 'nodejs',
variant = 'request',
options = {
@@ -189,21 +190,21 @@ This command will install all the dependencies in production mode.
```bash
$ npm install;
```
To install dev dependencies also for all codegens run:
To install dev dependencies also for all codegens run:
```bash
$ npm run deepinstall dev;
$ npm run deepinstall dev;
```
### Testing
### Testing
To run common repo test as well as tests (common structure test + individual codegen tests) for all the codegens
```bash
$ npm test;
$ npm test;
```
To run structure and individual tests on a single codegen
```bash
$ npm test <codegen-name>;
# Here "codege-name" is the folder name of the codegen inside codegens folder
```
### Packaging
### Packaging
To create zipped package of all codegens
```bash
$ npm run package;
41 changes: 41 additions & 0 deletions codegens/js-urlfetchapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Coverage directory used by tools like istanbul
.coverage

# node-waf configuration
.lock-wscript


# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

out/
76 changes: 76 additions & 0 deletions codegens/js-urlfetchapp/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
### NPM Specific: Disregard recursive project files
### ===============================================
/.editorconfig
/.gitmodules
/test

### Borrowed from .gitignore
### ========================

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Prevent IDE stuff
.idea
.vscode
*.sublime-*

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
.coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

snippet.swift

out/
44 changes: 44 additions & 0 deletions codegens/js-urlfetchapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

> Converts Postman-SDK Request into code snippet for Google Apps Script (UrlFetchApp).
#### Prerequisites
To run Code-Gen, ensure that you have NodeJS >= v8. A copy of the NodeJS installable can be downloaded from https://nodejs.org/en/download/package-manager.

## Using the Module
The module will expose an object which will have property `convert` which is the function for converting the Postman-SDK request to swift code snippet.

### convert function
Convert function takes three parameters

* `request` - Postman-SDK Request Object

* `options` - options is an object which hsa following properties
* `indentType` - String denoting type of indentation for code snippet. eg: 'Space', 'Tab'
* `indentCount` - The number of indentation characters to add per code level
* `trimRequestBody` - Whether or not request body fields should be trimmed

* `callback` - callback function with first parameter as error and second parameter as string for code snippet

##### Example:
```js
var request = new sdk.Request('www.google.com'), //using postman sdk to create request
options = {
indentCount: 3,
indentType: 'Space',
requestTimeout: 200,
trimRequestBody: true
};
convert(request, options, function(error, snippet) {
if (error) {
// handle error
}
// handle snippet
});
```
### Guidelines for using generated snippet

* Since Postman-SDK Request object doesn't provide complete path of the file, it needs to be manually inserted in case of uploading a file.

* This module doesn't support cookies.

* This module doesn't support requestTimeout option.
1 change: 1 addition & 0 deletions codegens/js-urlfetchapp/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./lib');
310 changes: 310 additions & 0 deletions codegens/js-urlfetchapp/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
var _ = require('./lodash'),
sanitize = require('./util').sanitize,
sanitizeOptions = require('./util').sanitizeOptions,
addFormParam = require('./util').addFormParam,
path = require('path');

/**
* Parses URLEncoded body from request
*
* @param {*} body URLEncoded Body
*/
function parseURLEncodedBody (body) {
var payload = [],
bodySnippet;
_.forEach(body, function (data) {
if (!data.disabled) {
payload.push(`${encodeURIComponent(data.key)} = ${encodeURIComponent(data.value)}`);
}
});
bodySnippet = `var formData = "${payload.join('&')}";\n`;
return bodySnippet;
}

/**
* Parses Raw data
*
* @param {*} body Raw body data
* @param {*} trim trim body option
* @param {String} contentType Content type of the body being sent
*/
function parseRawBody (body, trim, contentType) {
var bodySnippet = 'var formData = ';
// Match any application type whose underlying structure is json
// For example application/vnd.api+json
// All of them have +json as suffix
if (contentType && (contentType === 'application/json' || contentType.match(/\+json$/))) {
try {
let jsonBody = JSON.parse(body);
bodySnippet += `${JSON.stringify(jsonBody)};\n`;
}
catch (error) {
bodySnippet += `"${sanitize(body.toString(), trim)}";\n`;
}
}
else {
bodySnippet += `"${sanitize(body.toString(), trim)}";\n`;
}
return bodySnippet;
}

/**
* Parses graphql data
*
* @param {Object} body graphql body data
* @param {boolean} trim trim body option
* @param {String} indentString indentation to be added to the snippet
*/
function parseGraphQL (body, trim, indentString) {
let query = body.query,
graphqlVariables,
bodySnippet;
try {
graphqlVariables = JSON.parse(body.variables);
}
catch (e) {
graphqlVariables = {};
}
bodySnippet = 'var formData = {\n';
bodySnippet += `${indentString}query: "${sanitize(query, trim)}",\n`;
bodySnippet += `${indentString}variables: ${JSON.stringify(graphqlVariables)}\n`;
bodySnippet += '};\n';
return bodySnippet;
}

/**
* Parses formData body from request
*
* @param {*} body formData Body
* @param {*} trim trim body option
*/
function parseFormData (body, trim) {
var bodySnippet = 'var formData = {\n';
_.forEach(body, (data) => {
if (!(data.disabled)) {
/* istanbul ignore next */
/* ignoring because the file src is not stored in postman collection" */
if (data.type === 'file') {
var pathArray = data.src.split(path.sep),
fileName = pathArray[pathArray.length - 1];
bodySnippet += ` "${sanitize(data.key, trim)}": DriveApp.getFileById(${fileName}).getBlob(),\n`;
}
else {
bodySnippet += ` "${sanitize(data.key, trim)}": "${sanitize(data.value, trim)}",\n`;
}
}
});
bodySnippet += '};\n';
return bodySnippet;
}

/* istanbul ignore next */
/* ignoring because source of file is not stored in postman collection */
/**
* Parses file body from the Request
*
*/
function parseFile () {
// var bodySnippet = 'var data = new FormData();\n';
// bodySnippet += `data.append("${sanitize(body.key, trim)}", "${sanitize(body.src, trim)}", `;
// bodySnippet += `"${sanitize(body.key, trim)}");\n`;
var bodySnippet = 'var formData = "<file contents here>";\n';
return bodySnippet;
}

/**
* Parses Body from the Request
*
* @param {*} body body object from request.
* @param {*} trim trim body option
* @param {String} indentString indentation to be added to the snippet
* @param {String} contentType Content type of the body being sent
*/
function parseBody (body, trim, indentString, contentType) {
if (!_.isEmpty(body)) {
switch (body.mode) {
case 'urlencoded':
return parseURLEncodedBody(body.urlencoded, trim);
case 'raw':
return parseRawBody(body.raw, trim, contentType);
case 'graphql':
return parseGraphQL(body.graphql, trim, indentString);
case 'formdata':
return parseFormData(body.formdata, trim);
case 'file':
return parseFile(body.file, trim);
default:
return 'var formData = {};\n';
}
}
return 'var formData = {};\n';
}

/**
* Parses headers from the request.
*
* @param {Object} headers headers from the request.
*/
function parseHeaders (headers) {
var headerSnippet = 'var header = {\n';
if (!_.isEmpty(headers)) {
headers = _.reject(headers, 'disabled');
_.forEach(headers, function (header) {
headerSnippet += ` "${sanitize(header.key, true)}": "${sanitize(header.value)}",\n`;
});
}
headerSnippet += '};\n';
return headerSnippet;
}

/**
* Used to get the options specific to this codegen
*
* @returns {Array} - Returns an array of option objects
*/
function getOptions () {
return [
{
name: 'Set indentation count',
id: 'indentCount',
type: 'positiveInteger',
default: 2,
description: 'Set the number of indentation characters to add per code level'
},
{
name: 'Set indentation type',
id: 'indentType',
type: 'enum',
availableOptions: ['Tab', 'Space'],
default: 'Space',
description: 'Select the character used to indent lines of code'
},
{
name: 'Set request timeout',
id: 'requestTimeout',
type: 'positiveInteger',
default: 0,
description: 'Set number of milliseconds the request should wait for a response' +
' before timing out (use 0 for infinity)'
},
{
name: 'Trim request body fields',
id: 'trimRequestBody',
type: 'boolean',
default: false,
description: 'Remove white space and additional lines that may affect the server\'s response'
}
];
}

/**
* @description Converts Postman sdk request object to nodejs(unirest) code snippet
* @param {Object} request - postman-SDK request object
* @param {Object} options
* @param {String} options.indentType - type for indentation eg: Space, Tab
* @param {String} options.indentCount - number of spaces or tabs for indentation.
* @param {Boolean} options.trimRequestBody - whether to trim fields in request body or not
* @param {Number} options.requestTimeout : time in milli-seconds after which request will bail out
* @param {Function} callback - callback function with parameters (error, snippet)
*/
function convert (request, options, callback) {

if (!_.isFunction(callback)) {
throw new Error('JS-URLFETCHAPP-Converter: callback is not valid function');
}
options = sanitizeOptions(options, getOptions());
var indent, trim, headerSnippet,
codeSnippet = '',
bodySnippet = '';
indent = options.indentType === 'Tab' ? '\t' : ' ';
indent = indent.repeat(options.indentCount);
trim = options.trimRequestBody;

// The following code handles multiple files in the same formdata param.
// It removes the form data params where the src property is an array of filepath strings
// Splits that array into different form data params with src set as a single filepath string
if (request.body && request.body.mode === 'formdata') {
let formdata = request.body.formdata,
formdataArray = [];
formdata.members.forEach((param) => {
let key = param.key,
type = param.type,
disabled = param.disabled,
contentType = param.contentType;
// check if type is file or text
if (type === 'file') {
// if src is not of type string we check for array(multiple files)
if (typeof param.src !== 'string') {
// if src is an array(not empty), iterate over it and add files as separate form fields
if (Array.isArray(param.src) && param.src.length) {
param.src.forEach((filePath) => {
addFormParam(formdataArray, key, param.type, filePath, disabled, contentType);
});
}
// if src is not an array or string, or is an empty array, add a placeholder for file path(no files case)
else {
addFormParam(formdataArray, key, param.type, '/path/to/file', disabled, contentType);
}
}
// if src is string, directly add the param with src as filepath
else {
addFormParam(formdataArray, key, param.type, param.src, disabled, contentType);
}
}
// if type is text, directly add it to formdata array
else {
addFormParam(formdataArray, key, param.type, param.value, disabled, contentType);
}
});
request.body.update({
mode: 'formdata',
formdata: formdataArray
});
}
bodySnippet = request.body && !_.isEmpty(request.body.toJSON()) ? parseBody(request.body.toJSON(), trim,
indent, request.headers.get('Content-Type')) : 'var formData = {};\n';

codeSnippet += bodySnippet + '\n';

// codeSnippet += 'var xhr = new XMLHttpRequest();\nxhr.withCredentials = true;\n\n';

// codeSnippet += 'xhr.addEventListener("readystatechange", function() {\n';
// codeSnippet += `${indent}if(this.readyState === 4) {\n`;
// codeSnippet += `${indent.repeat(2)}console.log(this.responseText);\n`;
// codeSnippet += `${indent}}\n});\n\n`;

// codeSnippet += 'var xhr = new XMLHttpRequest();\nxhr.withCredentials = true;\n\n';


if (options.requestTimeout) {
// requestTimeout not supported
// See https://issuetracker.google.com/issues/36761852 for more information
codeSnippet += '\n// requestTimeout not supported\n';
codeSnippet += '// See https://issuetracker.google.com/issues/36761852 for more information \n\n';
}

headerSnippet = parseHeaders(request.toJSON().header);

codeSnippet += headerSnippet + '\n';

codeSnippet += 'var options = {\n';
codeSnippet += `${indent}'method' : '${request.method}',\n`;
codeSnippet += `${indent}'payload' : formData,\n`;
codeSnippet += `${indent}'header' : header,\n`;
codeSnippet += '};\n';
codeSnippet += '\n';

if (request.body && request.body.mode === 'graphql' && !request.headers.has('Content-Type')) {
codeSnippet += `var response = UrlFetchApp.fetch(encodeURI('${encodeURI(request.url.toString())}'), options);\n`;
}
else {
codeSnippet += `var response = UrlFetchApp.fetch('${encodeURI(request.url.toString())}', options);\n\n`;
}
codeSnippet += 'Logger.log(response.getContentText());\n';
callback(null, codeSnippet);
}

module.exports = {
convert: convert,
getOptions: getOptions
};
455 changes: 455 additions & 0 deletions codegens/js-urlfetchapp/lib/lodash.js

Large diffs are not rendered by default.

121 changes: 121 additions & 0 deletions codegens/js-urlfetchapp/lib/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
module.exports = {
/**
* sanitizes input string by handling escape characters eg: converts '''' to '\'\''
* and trim input if required
*
* @param {String} inputString
* @param {Boolean} [trim] - indicates whether to trim string or not
* @returns {String}
*/
sanitize: function (inputString, trim) {
if (typeof inputString !== 'string') {
return '';
}
inputString = inputString.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r');
return trim ? inputString.trim() : inputString;

},

/**
* sanitizes input options
*
* @param {Object} options - Options provided by the user
* @param {Array} optionsArray - options array received from getOptions function
*
* @returns {Object} - Sanitized options object
*/
sanitizeOptions: function (options, optionsArray) {
var result = {},
defaultOptions = {},
id;
optionsArray.forEach((option) => {
defaultOptions[option.id] = {
default: option.default,
type: option.type
};
if (option.type === 'enum') {
defaultOptions[option.id].availableOptions = option.availableOptions;
}
});

for (id in options) {
if (options.hasOwnProperty(id)) {
if (defaultOptions[id] === undefined) {
continue;
}
switch (defaultOptions[id].type) {
case 'boolean':
if (typeof options[id] !== 'boolean') {
result[id] = defaultOptions[id].default;
}
else {
result[id] = options[id];
}
break;
case 'positiveInteger':
if (typeof options[id] !== 'number' || options[id] < 0) {
result[id] = defaultOptions[id].default;
}
else {
result[id] = options[id];
}
break;
case 'enum':
if (!defaultOptions[id].availableOptions.includes(options[id])) {
result[id] = defaultOptions[id].default;
}
else {
result[id] = options[id];
}
break;
default:
result[id] = options[id];
}
}
}

for (id in defaultOptions) {
if (defaultOptions.hasOwnProperty(id)) {
if (result[id] === undefined) {
result[id] = defaultOptions[id].default;
}
}
}
return result;
},

/**
*
* @param {Array} array - form data array
* @param {String} key - key of form data param
* @param {String} type - type of form data param(file/text)
* @param {String} val - value/src property of form data param
* @param {String} disabled - Boolean denoting whether the param is disabled or not
* @param {String} contentType - content type header of the param
*
* Appends a single param to form data array
*/
addFormParam: function (array, key, type, val, disabled, contentType) {
if (type === 'file') {
array.push({
key: key,
type: type,
src: val,
disabled: disabled,
contentType: contentType
});
}
else {
array.push({
key: key,
type: type,
value: val,
disabled: disabled,
contentType: contentType
});
}
}
};
5 changes: 5 additions & 0 deletions codegens/js-urlfetchapp/npm-shrinkwrap.json
56 changes: 56 additions & 0 deletions codegens/js-urlfetchapp/npm/test-lint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env node
var shell = require('shelljs'),
chalk = require('chalk'),
async = require('async'),
ESLintCLIEngine = require('eslint').CLIEngine,

/**
* The list of source code files / directories to be linted.
*
* @type {Array}
*/
LINT_SOURCE_DIRS = [
'./lib',
'./test',
'./npm/*.js',
'./index.js'
];

module.exports = function (exit) {
// banner line
console.info(chalk.yellow.bold('\nLinting files using eslint...'));

async.waterfall([

/**
* Instantiates an ESLint CLI engine and runs it in the scope defined within LINT_SOURCE_DIRS.
*
* @param {Function} next - The callback function whose invocation marks the end of the lint test run.
* @returns {*}
*/
function (next) {
next(null, (new ESLintCLIEngine()).executeOnFiles(LINT_SOURCE_DIRS));
},

/**
* Processes a test report from the Lint test runner, and displays meaningful results.
*
* @param {Object} report - The overall test report for the current lint test.
* @param {Object} report.results - The set of test results for the current lint run.
* @param {Function} next - The callback whose invocation marks the completion of the post run tasks.
* @returns {*}
*/
function (report, next) {
var errorReport = ESLintCLIEngine.getErrorResults(report.results);
// log the result to CLI
console.info(ESLintCLIEngine.getFormatter()(report.results));
// log the success of the parser if it has no errors
(errorReport && !errorReport.length) && console.info(chalk.green('eslint ok!'));
// ensure that the exit code is non zero in case there was an error
next(Number(errorReport && errorReport.length) || 0);
}
], exit);
};

// ensure we run this script exports if this is a direct stdin.tty run
!module.parent && module.exports(shell.exit);
59 changes: 59 additions & 0 deletions codegens/js-urlfetchapp/npm/test-newman.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
/* eslint-env node, es6 */
// ---------------------------------------------------------------------------------------------------------------------
// This script is intended to execute all unit tests.
// ---------------------------------------------------------------------------------------------------------------------

var shell = require('shelljs'),

// set directories and files for test and coverage report
path = require('path'),

NYC = require('nyc'),
chalk = require('chalk'),
recursive = require('recursive-readdir'),

COV_REPORT_PATH = '.coverage',
SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'newman');

module.exports = function (exit) {
// banner line
console.info(chalk.yellow.bold('Running newman tests using mocha on node...'));

shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH);
shell.mkdir('-p', COV_REPORT_PATH);

var Mocha = require('mocha'),
nyc = new NYC({
reportDir: COV_REPORT_PATH,
tempDirectory: COV_REPORT_PATH,
reporter: ['text', 'lcov', 'text-summary'],
exclude: ['config', 'test'],
hookRunInContext: true,
hookRunInThisContext: true
});

nyc.wrap();
// add all spec files to mocha
recursive(SPEC_SOURCE_DIR, function (err, files) {
if (err) { console.error(err); return exit(1); }

var mocha = new Mocha({ timeout: 1000 * 60 });

files.filter(function (file) { // extract all test files
return (file.substr(-8) === '.test.js');
}).forEach(mocha.addFile.bind(mocha));

mocha.run(function (runError) {
runError && console.error(runError.stack || runError);

nyc.reset();
nyc.writeCoverageFile();
nyc.report();
exit(runError ? 1 : 0);
});
});
};

// ensure we run this script exports if this is a direct stdin.tty run
!module.parent && module.exports(shell.exit);
59 changes: 59 additions & 0 deletions codegens/js-urlfetchapp/npm/test-unit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
/* eslint-env node, es6 */
// ---------------------------------------------------------------------------------------------------------------------
// This script is intended to execute all unit tests.
// ---------------------------------------------------------------------------------------------------------------------

var shell = require('shelljs'),

// set directories and files for test and coverage report
path = require('path'),

NYC = require('nyc'),
chalk = require('chalk'),
recursive = require('recursive-readdir'),

COV_REPORT_PATH = '.coverage',
SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'unit');

module.exports = function (exit) {
// banner line
console.info(chalk.yellow.bold('Running unit tests using mocha on node...'));

shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH);
shell.mkdir('-p', COV_REPORT_PATH);

var Mocha = require('mocha'),
nyc = new NYC({
reportDir: COV_REPORT_PATH,
tempDirectory: COV_REPORT_PATH,
reporter: ['text', 'lcov', 'text-summary'],
exclude: ['config', 'test'],
hookRunInContext: true,
hookRunInThisContext: true
});

nyc.wrap();
// add all spec files to mocha
recursive(SPEC_SOURCE_DIR, function (err, files) {
if (err) { console.error(err); return exit(1); }

var mocha = new Mocha({ timeout: 1000 * 60 });

files.filter(function (file) { // extract all test files
return (file.substr(-8) === '.test.js');
}).forEach(mocha.addFile.bind(mocha));

mocha.run(function (runError) {
runError && console.error(runError.stack || runError);

nyc.reset();
nyc.writeCoverageFile();
nyc.report();
exit(runError ? 1 : 0);
});
});
};

// ensure we run this script exports if this is a direct stdin.tty run
!module.parent && module.exports(shell.exit);
19 changes: 19 additions & 0 deletions codegens/js-urlfetchapp/npm/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env node
var chalk = require('chalk'),
exit = require('shelljs').exit,
prettyms = require('pretty-ms'),
startedAt = Date.now(),
name = require('../package.json').name;

require('async').series([
require('./test-lint'),
// require('./test-newman'),
// Add a separate folder for every new suite of tests
require('./test-unit')
// require('./test-browser')
// require('./test-integration')
], function (code) {
// eslint-disable-next-line max-len
console.info(chalk[code ? 'red' : 'green'](`\n${name}: duration ${prettyms(Date.now() - startedAt)}\n${name}: ${code ? 'not ok' : 'ok'}!`));
exit(code && (typeof code === 'number' ? code : 1) || 0);
});
33 changes: 33 additions & 0 deletions codegens/js-urlfetchapp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@postman/codegen-js-urlfetchapp",
"version": "0.0.1",
"description": "Converts Postman SDK Requests to Google Apps Script UrlFetchApp",
"main": "index.js",
"com_postman_plugin": {
"type": "code_generator",
"lang": "JavaScript",
"variant": "UrlFetchApp",
"syntax_mode": "javascript"
},
"directories": {
"lib": "lib",
"test": "test"
},
"scripts": {
"test": "node npm/test.js",
"test-lint": "node npm/test-lint.js",
"test-newman": "node npm/test-newman.js"
},
"repository": {
"type": "git",
"url": ""
},
"author": "Postman Labs <help@getpostman.com>",
"license": "Apache-2.0",
"homepage": "https://github.com/postmanlabs/code-generators/tree/master/codegens/js-urlfetchapp",
"dependencies": {},
"devDependencies": {},
"engines": {
"node": ">=8"
}
}
23 changes: 23 additions & 0 deletions codegens/js-urlfetchapp/test/newman/newman.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var runNewmanTest = require('../../../../test/codegen/newman/newmanTestUtil').runNewmanTest,
convert = require('../../index').convert;


describe('js-urlfetchapp Converter', function () {
describe('convert for different request types', function () {
var options = {
indentType: 'Space',
indentCount: 4
},
testConfig = {
// filename along with the appropriate version of the file. This file will be used to run the snippet.
fileName: 'snippet.js',
// Run script required to run the generated code snippet
runScript: 'node snippet.js',
// Compile script required to compile the code snippet
compileScript: '',
// Array of name of collections for which newman tests has to be skipped.
skipCollections: []
};
runNewmanTest(convert, options, testConfig);
});
});
Empty file.
373 changes: 373 additions & 0 deletions codegens/js-urlfetchapp/test/unit/convert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
var expect = require('chai').expect,
sdk = require('postman-collection'),
sanitize = require('../../lib/util.js').sanitize,

convert = require('../../index').convert,
getOptions = require('../../index').getOptions,

fs = require('fs');

function save_snippet (path, str) {
fs.writeFile(path, str, function (err) { return err; });
return 0;
}


describe('js-urlfetchapp convert function', function () {

it('should trim header keys and not trim header values', function () {
var request = new sdk.Request({
'method': 'GET',
'header': [
{
'key': ' key_containing_whitespaces ',
'value': ' value_containing_whitespaces '
}
],
'url': {
'raw': 'https://google.com',
'protocol': 'https',
'host': [
'google',
'com'
]
}
});
convert(request, {}, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}
expect(snippet).to.be.a('string');
expect(snippet).to.include('"key_containing_whitespaces": " value_containing_whitespaces "');
});
});

it('should not include JSON.stringify in the snippet for raw json bodies', function () {
var request = new sdk.Request({
'method': 'POST',
'header': [
{
'key': 'Content-Type',
'value': 'application/json'
}
],
'body': {
'mode': 'raw',
'raw': '{\n "json": "Test-Test"\n}'
},
'url': {
'raw': 'https://postman-echo.com/post',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'post'
]
}
});
convert(request, {}, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}
save_snippet('./unitTestSnippets/raw_json.js', snippet);
expect(snippet).to.be.a('string');
expect(snippet).to.not.include('JSON.stringify');
});
});

it('should not use JSON.stringify if the content-type is application/vnd.api+json', function () {
let request = new sdk.Request({
'method': 'POST',
'header': [
{
'key': 'Content-Type',
'value': 'application/vnd.api+json'
}
],
'body': {
'mode': 'raw',
'raw': '{"data": {"hello": "world"} }'
},
'url': {
'raw': 'https://postman-echo.com/post',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'get'
]
}
});
convert(request, {}, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}
save_snippet('./unitTestSnippets/vnd.api_json.js', snippet);
expect(snippet).to.be.a('string');
expect(snippet).to.not.contain('JSON.stringify');
});
});

it('should generate snippets for no files in form data', function () {
var request = new sdk.Request({
'method': 'POST',
'header': [],
'body': {
'mode': 'formdata',
'formdata': [
{
'key': 'no file',
'value': '',
'type': 'file',
'src': []
},
{
'key': 'no src',
'value': '',
'type': 'file'
},
{
'key': 'invalid src',
'value': '',
'type': 'file',
'src': {}
}
]
},
'url': {
'raw': 'https://postman-echo.com/post',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'post'
]
}
});
convert(request, {}, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}
save_snippet('./unitTestSnippets/no_file.js', snippet);
expect(snippet).to.be.a('string');
expect(snippet).to.include('"no src": DriveApp.getFileById(file).getBlob()');
expect(snippet).to.include('"no src": DriveApp.getFileById(file).getBlob()');
expect(snippet).to.include('"invalid src": DriveApp.getFileById(file).getBlob()');
});
});

describe('Sanitize function', function () {
it('should return empty string when input is not a string type', function () {
expect(sanitize(123, false)).to.equal('');
expect(sanitize(null, false)).to.equal('');
expect(sanitize({}, false)).to.equal('');
expect(sanitize([], false)).to.equal('');
});
it('should trim input string when needed', function () {
expect(sanitize('inputString ', true)).to.equal('inputString');
});
});

describe('POST Form data Request', function () {
var req = {
'method': 'POST',
'header': [
{
'key': 'Content-Type',
'value': 'application/x-www-form-urlencoded',
'disabled': true
},
{
'key': 'Content-Type',
'value': 'application/json',
'disabled': true
}
],
'body': {
'mode': 'formdata',
'formdata': [
{
'key': 'fdjks',
'value': 'dsf',
'description': '',
'type': 'text',
'disabled': true
},
{
'key': 'sdf',
'value': 'helo',
'description': '',
'type': 'text'
},
{
'key': '12',
'value': '"23"',
'description': '',
'type': 'text'
},
{
'key': '\'123\'',
'value': '1234',
'description': '',
'type': 'text'
}
]
},
'url': {
'raw': 'https://postman-echo.com/post',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'post'
]
},
'description': 'The HTTP `POST` request with formData'
},

request = new sdk.Request(req),
options = {
indentCount: 2,
indentType: 'Space',
trimRequestBody: false,
requestTimeout: 3000
};
convert(request, options, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
return;
}
save_snippet('./unitTestSnippets/post_formData.js', snippet);

it('should not be empty', function () {
expect(snippet).not.to.equal('');
});

// it('should contain formData object', function () {
// expect(snippet).to.deep.include('var data = new FormData()');
// expect(snippet).to.deep.include('data.append("sdf", "helo")');
// });

it('should show timeout option not supported warning when timeout is set to non zero value', function () {
expect(snippet).to.include('requestTimeout not supported');
});
});
});

describe('Request with no body', function () {
var req = {
'method': 'GET',
'url': {
'raw': 'https://postman-echo.com/get',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'get'
]
},
'description': 'Request without a body'
},

request = new sdk.Request(req),
options = {
indentCount: 2,
indentType: 'Space'
};
convert(request, options, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
return;
}
save_snippet('./unitTestSnippets/no_body_request.js', snippet);

it('should not be empty', function () {
expect(snippet).not.to.equal('');
});
it('should contain var formData = {}', function () {
expect(snippet).to.deep.include('var formData = {}');
});
it('should contain var response = UrlFetchApp.fetch', function () {
expect(snippet).to.deep.include('var response = UrlFetchApp.fetch(');
});
});
});
describe('Request with empty body', function () {
var req = {
'method': 'GET',
'body': {},
'url': {
'raw': 'https://postman-echo.com/get',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'get'
]
},
'description': 'Request without a body'
},

request = new sdk.Request(req),
options = {
indentCount: 2,
indentType: 'Space'
};
convert(request, options, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
return;
}

save_snippet('./unitTestSnippets/empty_body_request.js', snippet);

it('should not be empty', function () {
expect(snippet).not.to.equal('');
});
it('should contain var formData = {}', function () {
expect(snippet).to.deep.include('var formData = {}');
});
it('should contain var response = UrlFetchApp.fetch', function () {
expect(snippet).to.deep.include('var response = UrlFetchApp.fetch');
});
});
});
describe('getOptions function', function () {
var options = getOptions();
it('should return an array of specific options', function () {
expect(options).to.be.an('array');
});
it('should have 2 as default indent count ', function () {
expect(options[0].default).to.equal(2);
});
it('should have Space as default indent type ', function () {
expect(options[1].default).to.equal('Space');
});
it('should have 0 as default request timeout ', function () {
expect(options[2].default).to.equal(0);
});
it('should have default body trim set as false', function () {
expect(options[3].default).to.equal(false);
});
});
describe('convert function', function () {
it('should throw an error if callback is not a function', function () {
var request = null,
options = [],
callback = null;
expect(function () { convert(request, options, callback); }).to.throw(Error);
});
});
});
1,397 changes: 1,397 additions & 0 deletions codegens/js-urlfetchapp/test/unit/fixtures/testcollection/collection.json

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions codegens/js-urlfetchapp/test/unit/validation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
var expect = require('chai').expect,
path = require('path'),

package = require(path.resolve('.', 'package.json'));


describe('package.json', function () {
it('should have com_postman_plugin object with valid properties', function () {
expect(package).to.have.property('com_postman_plugin');

expect(package.com_postman_plugin.type).to.equal('code_generator');
expect(package.com_postman_plugin.lang).to.be.a('string');
expect(package.com_postman_plugin.variant).to.be.a('string');
expect(package.com_postman_plugin.syntax_mode).to.be.a('string');
});
it('should have main property with relative path to object with convert property', function () {
var languageModule;

expect(package.main).to.be.a('string');

try {
languageModule = require(path.resolve('.', package.main));
}
catch (error) {
console.error(error);
}
expect(languageModule).to.be.a('object');
expect(languageModule.convert).to.be.a('function');
});
});
14 changes: 14 additions & 0 deletions codegens/js-urlfetchapp/unitTestSnippets/empty_body_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var formData = {};

var header = {
};

var options = {
'method' : 'GET',
'payload' : formData,
'header' : header,
};

var response = UrlFetchApp.fetch('https://postman-echo.com/get', options);

Logger.log(response.getContentText());
14 changes: 14 additions & 0 deletions codegens/js-urlfetchapp/unitTestSnippets/no_body_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var formData = {};

var header = {
};

var options = {
'method' : 'GET',
'payload' : formData,
'header' : header,
};

var response = UrlFetchApp.fetch('https://postman-echo.com/get', options);

Logger.log(response.getContentText());
18 changes: 18 additions & 0 deletions codegens/js-urlfetchapp/unitTestSnippets/no_file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var formData = {
"no file": DriveApp.getFileById(file).getBlob(),
"no src": DriveApp.getFileById(file).getBlob(),
"invalid src": DriveApp.getFileById(file).getBlob(),
};

var header = {
};

var options = {
'method' : 'POST',
'payload' : formData,
'header' : header,
};

var response = UrlFetchApp.fetch('https://postman-echo.com/post', options);

Logger.log(response.getContentText());
22 changes: 22 additions & 0 deletions codegens/js-urlfetchapp/unitTestSnippets/post_formData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
var formData = {
"sdf": "helo",
"12": "\"23\"",
"'123'": "1234",
};


// requestTimeout not supported
// See https://issuetracker.google.com/issues/36761852 for more information

var header = {
};

var options = {
'method' : 'POST',
'payload' : formData,
'header' : header,
};

var response = UrlFetchApp.fetch('https://postman-echo.com/post', options);

Logger.log(response.getContentText());
15 changes: 15 additions & 0 deletions codegens/js-urlfetchapp/unitTestSnippets/raw_json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
var formData = {"json":"Test-Test"};

var header = {
"Content-Type": "application/json",
};

var options = {
'method' : 'POST',
'payload' : formData,
'header' : header,
};

var response = UrlFetchApp.fetch('https://postman-echo.com/post', options);

Logger.log(response.getContentText());
15 changes: 15 additions & 0 deletions codegens/js-urlfetchapp/unitTestSnippets/vnd.api_json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
var formData = {"data":{"hello":"world"}};

var header = {
"Content-Type": "application/vnd.api+json",
};

var options = {
'method' : 'POST',
'payload' : formData,
'header' : header,
};

var response = UrlFetchApp.fetch('https://postman-echo.com/get', options);

Logger.log(response.getContentText());