Merge branch 'master' of github.com:gschier/insomnia

This commit is contained in:
Gregory Schier
2017-02-20 10:32:34 -08:00
24 changed files with 525 additions and 144 deletions

View File

@@ -1,6 +1,6 @@
import * as models from '../../models';
export default {
export const data = {
[models.workspace.type]: [{
_id: 'wrk_1',
name: 'Wrk 1'

View File

@@ -1,14 +1,16 @@
import * as db from '../database';
import * as models from '../../models';
import * as db from '../database';
function loadFixture (name) {
const fixtures = require(`../__fixtures__/${name}`);
const fixtures = require(`../__fixtures__/${name}`).data;
const promises = [];
for (const type of Object.keys(fixtures)) {
for (const doc of fixtures[type]) {
promises.push(db.insert(Object.assign({}, doc, {type})));
}
}
return Promise.all(promises);
}
describe('init()', () => {
@@ -86,9 +88,7 @@ describe('bufferChanges()', () => {
});
describe('requestCreate()', () => {
beforeEach(() => {
return db.init(models.types(), {inMemoryOnly: true}, true);
});
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
it('creates a valid request', async () => {
const now = Date.now();

View File

@@ -4,29 +4,33 @@ import * as models from '../../models';
jest.mock('electron');
describe('render()', () => {
it('renders hello world', () => {
const rendered = renderUtils.render('Hello {{ msg }}!', {msg: 'World'});
it('renders hello world', async () => {
const rendered = await renderUtils.render('Hello {{ msg }}!', {msg: 'World'});
expect(rendered).toBe('Hello World!');
});
it('renders custom tag: uuid', () => {
const rendered = renderUtils.render('Hello {% uuid %}!');
it('renders custom tag: uuid', async () => {
const rendered = await renderUtils.render('Hello {% uuid %}!');
expect(rendered).toMatch(/Hello [a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}!/);
});
it('renders custom tag: timestamp', () => {
const rendered = renderUtils.render('Hello {% timestamp %}!');
it('renders custom tag: timestamp', async () => {
const rendered = await renderUtils.render('Hello {% timestamp %}!');
expect(rendered).toMatch(/Hello \d{13}!/);
});
it('fails on invalid template', () => {
const fn = () => renderUtils.render('Hello {{ msg }!', {msg: 'World'});
expect(fn).toThrowError('expected variable end');
it('fails on invalid template', async () => {
try {
await renderUtils.render('Hello {{ msg }!', {msg: 'World'});
fail('Render should have failed');
} catch (err) {
expect(err.message).toContain('expected variable end');
}
});
});
describe('buildRenderContext()', () => {
it('cascades properly', () => {
it('cascades properly', async () => {
const ancestors = [
{
type: models.requestGroup.type,
@@ -48,7 +52,7 @@ describe('buildRenderContext()', () => {
data: {foo: 'sub', sub: true}
};
const context = renderUtils.buildRenderContext(
const context = await renderUtils.buildRenderContext(
ancestors,
rootEnvironment,
subEnvironment
@@ -62,14 +66,14 @@ describe('buildRenderContext()', () => {
});
});
it('rendered recursive should not infinite loop', () => {
it('rendered recursive should not infinite loop', async () => {
const ancestors = [{
// Sub Environment
type: models.requestGroup.type,
environment: {recursive: '{{ recursive }}/hello'}
}];
const context = renderUtils.buildRenderContext(ancestors);
const context = await renderUtils.buildRenderContext(ancestors);
// This is longer than 3 because it multiplies every time (1 -> 2 -> 4 -> 8)
expect(context).toEqual({
@@ -77,7 +81,7 @@ describe('buildRenderContext()', () => {
});
});
it('render up to 3 recursion levels', () => {
it('render up to 3 recursion levels', async () => {
const ancestors = [{
// Sub Environment
type: models.requestGroup.type,
@@ -90,7 +94,7 @@ describe('buildRenderContext()', () => {
}
}];
const context = renderUtils.buildRenderContext(ancestors);
const context = await renderUtils.buildRenderContext(ancestors);
expect(context).toEqual({
d: '/d',
@@ -101,7 +105,7 @@ describe('buildRenderContext()', () => {
});
});
it('rendered sibling environment variables', () => {
it('rendered sibling environment variables', async () => {
const ancestors = [{
// Sub Environment
type: models.requestGroup.type,
@@ -111,12 +115,12 @@ describe('buildRenderContext()', () => {
}
}];
const context = renderUtils.buildRenderContext(ancestors);
const context = await renderUtils.buildRenderContext(ancestors);
expect(context).toEqual({sibling: 'sibling', test: 'sibling/hello'});
});
it('rendered parent environment variables', () => {
it('rendered parent environment variables', async () => {
const ancestors = [{
name: 'Parent',
type: models.requestGroup.type,
@@ -131,12 +135,12 @@ describe('buildRenderContext()', () => {
}
}];
const context = renderUtils.buildRenderContext(ancestors);
const context = await renderUtils.buildRenderContext(ancestors);
expect(context).toEqual({grandparent: 'grandparent', test: 'grandparent parent'});
});
it('rendered parent same name environment variables', () => {
it('rendered parent same name environment variables', async () => {
const ancestors = [{
name: 'Parent',
type: models.requestGroup.type,
@@ -151,12 +155,12 @@ describe('buildRenderContext()', () => {
}
}];
const context = renderUtils.buildRenderContext(ancestors);
const context = await renderUtils.buildRenderContext(ancestors);
expect(context).toEqual({base_url: 'https://insomnia.rest/resource'});
});
it('rendered parent, ignoring sibling environment variables', () => {
it('rendered parent, ignoring sibling environment variables', async () => {
const ancestors = [{
name: 'Parent',
type: models.requestGroup.type,
@@ -180,13 +184,13 @@ describe('buildRenderContext()', () => {
}
}];
const context = renderUtils.buildRenderContext(ancestors);
const result = renderUtils.render('{{ urls.admin }}/foo', context);
const context = await renderUtils.buildRenderContext(ancestors);
const result = await renderUtils.render('{{ urls.admin }}/foo', context);
expect(result).toEqual('https://parent.com/admin/foo');
});
it('renders child environment variables', () => {
it('renders child environment variables', async () => {
const ancestors = [{
name: 'Parent',
type: models.requestGroup.type,
@@ -201,12 +205,12 @@ describe('buildRenderContext()', () => {
}
}];
const context = renderUtils.buildRenderContext(ancestors);
const context = await renderUtils.buildRenderContext(ancestors);
expect(context).toEqual({parent: 'parent', test: 'parent grandparent'});
});
it('cascades properly and renders', () => {
it('cascades properly and renders', async () => {
const ancestors = [
{
type: models.requestGroup.type,
@@ -235,7 +239,7 @@ describe('buildRenderContext()', () => {
data: {winner: 'root', root: true, base_url: 'ignore this'}
};
const context = renderUtils.buildRenderContext(ancestors,
const context = await renderUtils.buildRenderContext(ancestors,
rootEnvironment,
subEnvironment
);
@@ -250,12 +254,12 @@ describe('buildRenderContext()', () => {
});
});
it('works with minimal parameters', () => {
it('works with minimal parameters', async () => {
const ancestors = null;
const rootEnvironment = null;
const subEnvironment = null;
const context = renderUtils.buildRenderContext(
const context = await renderUtils.buildRenderContext(
ancestors,
rootEnvironment,
subEnvironment
@@ -266,8 +270,8 @@ describe('buildRenderContext()', () => {
});
describe('recursiveRender()', () => {
it('correctly renders simple Object', () => {
const newObj = renderUtils.recursiveRender({
it('correctly renders simple Object', async () => {
const newObj = await renderUtils.recursiveRender({
foo: '{{ foo }}',
bar: 'bar',
baz: '{{ bad }}'
@@ -280,13 +284,14 @@ describe('recursiveRender()', () => {
})
});
it('correctly renders complex Object', () => {
it('correctly renders complex Object', async () => {
const d = new Date();
const obj = {
foo: '{{ foo }}',
null: null,
bool: true,
date: d,
undef: undefined,
num: 1234,
nested: {
foo: '{{ foo }}',
@@ -294,13 +299,14 @@ describe('recursiveRender()', () => {
}
};
const newObj = renderUtils.recursiveRender(obj, {foo: 'bar'});
const newObj = await renderUtils.recursiveRender(obj, {foo: 'bar'});
expect(newObj).toEqual({
foo: 'bar',
null: null,
bool: true,
date: d,
undef: undefined,
num: 1234,
nested: {
foo: 'bar',
@@ -314,13 +320,16 @@ describe('recursiveRender()', () => {
expect(obj.nested.arr[2]).toBe('{{ foo }}');
});
it('fails on bad template', () => {
const fn = () => renderUtils.recursiveRender({
foo: '{{ foo }',
bar: 'bar',
baz: '{{ bad }}'
}, {foo: 'bar'});
expect(fn).toThrowError('expected variable end');
it('fails on bad template', async () => {
try {
await renderUtils.recursiveRender({
foo: '{{ foo }',
bar: 'bar',
baz: '{{ bad }}'
}, {foo: 'bar'});
fail('Render should have failed');
} catch (err) {
expect(err.message).toContain('expected variable end');
}
})
});

View File

@@ -1,65 +1,14 @@
import nunjucks from 'nunjucks';
import traverse from 'traverse';
import uuid from 'uuid';
import clone from 'clone';
import * as models from '../models';
import {getBasicAuthHeader, hasAuthHeader, setDefaultProtocol} from './misc';
import * as db from './database';
import * as templating from '../templating';
const nunjucksEnvironment = nunjucks.configure({
autoescape: false
});
class NoArgsExtension {
parse (parser, nodes, lexer) {
const args = parser.parseSignature(null, true);
parser.skip(lexer.TOKEN_BLOCK_END);
return new nodes.CallExtension(this, 'run', args);
}
export async function render (template, context = {}) {
return templating.render(template, {context});
}
// class ArgsExtension {
// parse (parser, nodes, lexer) {
// const tok = parser.nextToken();
// const args = parser.parseSignature(null, true);
// parser.advanceAfterBlockEnd(tok.value);
// return new nodes.CallExtension(this, 'run', args);
// }
// }
class TimestampExtension extends NoArgsExtension {
constructor () {
super();
this.tags = ['timestamp'];
}
run (context) {
return Date.now();
}
}
class UuidExtension extends NoArgsExtension {
constructor () {
super();
this.tags = ['uuid'];
}
run (context) {
return uuid.v4();
}
}
nunjucksEnvironment.addExtension('uuid', new UuidExtension());
nunjucksEnvironment.addExtension('timestamp', new TimestampExtension());
export function render (template, context = {}) {
try {
return nunjucksEnvironment.renderString(template, context);
} catch (e) {
throw new Error(e.message.replace(/\(unknown path\)\s*/, ''));
}
}
export function buildRenderContext (ancestors, rootEnvironment, subEnvironment) {
export async function buildRenderContext (ancestors, rootEnvironment, subEnvironment) {
if (!Array.isArray(ancestors)) {
ancestors = [];
}
@@ -88,7 +37,7 @@ export function buildRenderContext (ancestors, rootEnvironment, subEnvironment)
for (const environment of environments) {
// Do an Object.assign, but render each property as it overwrites. This
// way we can keep same-name variables from the parent context.
_objectDeepAssignRender(renderContext, environment);
await _objectDeepAssignRender(renderContext, environment);
}
// Render the context with itself to fill in the rest.
@@ -96,30 +45,59 @@ export function buildRenderContext (ancestors, rootEnvironment, subEnvironment)
// Render up to 5 levels of recursive references.
for (let i = 0; i < 3; i++) {
finalRenderContext = recursiveRender(finalRenderContext, finalRenderContext);
finalRenderContext = await recursiveRender(finalRenderContext, finalRenderContext);
}
return finalRenderContext;
}
export function recursiveRender (obj, context) {
/**
* Recursively render any JS object and return a new one
* @param {*} originalObj - object to render
* @param {object} context - context to render against
* @return {Promise.<*>}
*/
export async function recursiveRender (originalObj, context) {
const obj = clone(originalObj);
const toS = obj => Object.prototype.toString.call(obj);
// Make a copy so no one gets mad :)
const newObj = traverse.clone(obj);
traverse(newObj).forEach(function (x) {
try {
if (typeof x === 'string') {
const str = render(x, context);
this.update(str);
async function next (x) {
// Leave these types alone
if (
toS(x) === '[object Date]' ||
toS(x) === '[object RegExp]' ||
toS(x) === '[object Error]' ||
toS(x) === '[object Boolean]' ||
toS(x) === '[object Number]' ||
toS(x) === '[object Null]' ||
toS(x) === '[object Undefined]'
) {
// Do nothing to these types
} else if (toS(x) === '[object String]') {
try {
x = render(x, context);
} catch (err) {
// Failed to render Request
// const path = this.path.join('.');
const path = 'TODO"';
throw new Error(`Failed to render Request.${path}: "${err.message}"`);
}
} else if (Array.isArray(x)) {
for (let i = 0; i < x.length; i++) {
x[i] = await next(x[i]);
}
} else if (typeof x === 'object') {
const keys = Object.keys(x);
for (const key of keys) {
x[key] = await next(x[key]);
}
} catch (e) {
// Failed to render Request
const path = this.path.join('.');
throw new Error(`Failed to render Request.${path}: "${e.message}"`);
}
});
return newObj;
return x;
}
return next(obj);
}
export async function getRenderContext (request, environmentId, ancestors = null) {
@@ -147,7 +125,7 @@ export async function getRenderedRequest (request, environmentId) {
const renderContext = await getRenderContext(request, environmentId, ancestors);
// Render all request properties
const renderedRequest = recursiveRender(request, renderContext);
const renderedRequest = await recursiveRender(request, renderContext);
// Remove disabled params
renderedRequest.parameters = renderedRequest.parameters.filter(p => !p.disabled);
@@ -182,7 +160,7 @@ export async function getRenderedRequest (request, environmentId) {
return renderedRequest;
}
function _objectDeepAssignRender (base, obj) {
async function _objectDeepAssignRender (base, obj) {
for (const key of Object.keys(obj)) {
/*
* If we're overwriting a string, try to render it first with the base as
@@ -196,7 +174,7 @@ function _objectDeepAssignRender (base, obj) {
* original base_url of google.com would be lost.
*/
if (typeof base[key] === 'string') {
base[key] = render(obj[key], base);
base[key] = await render(obj[key], base);
} else {
base[key] = obj[key];
}

View File

@@ -1,6 +1,20 @@
let counter = 0;
let v1Counter = 0;
let v4Counter = 0;
const uuids = [
const v1UUIDs = [
'f7272c80-f493-11e6-bc64-92361f002671',
'f7272f0a-f493-11e6-bc64-92361f002671',
'f72733a6-f493-11e6-bc64-92361f002671',
'f72735c2-f493-11e6-bc64-92361f002671',
'f7273798-f493-11e6-bc64-92361f002671',
'f7273c3e-f493-11e6-bc64-92361f002671',
'f7273dba-f493-11e6-bc64-92361f002671',
'f7273eaa-f493-11e6-bc64-92361f002671',
'f7273f7c-f493-11e6-bc64-92361f002671',
'f7274300-f493-11e6-bc64-92361f002671',
];
const v4UUIDs = [
'dd2ccc1a-2745-477a-881a-9e8ef9d42403',
'e3e96e5f-dd68-4229-8b66-dee1f0940f3d',
'a262d22b-5fa8-491c-9bd9-58fba03e301e',
@@ -77,10 +91,19 @@ const uuids = [
'185dc090-3fcc-44e3-8a17-4e2fa792f91a',
];
function v4 () {
const uuid = uuids[counter++];
function v1 () {
const uuid = v1UUIDs[v1Counter++];
if (!uuid) {
throw new Error('Not enough mocked UUIDs to go around');
throw new Error('Not enough mocked v1 UUIDs to go around');
}
return uuid;
}
function v4 () {
const uuid = v4UUIDs[v4Counter++];
if (!uuid) {
throw new Error('Not enough mocked v4 UUIDs to go around');
}
return uuid;
@@ -88,3 +111,4 @@ function v4 () {
module.exports = () => v4();
module.exports.v4 = () => v4();
module.exports.v1 = () => v1();

View File

@@ -41,6 +41,11 @@ export function findRecentForRequest (requestId, limit) {
return db.findMostRecentlyModified(type, {parentId: requestId}, limit);
}
export async function getLatestForRequest (requestId) {
const responses = await findRecentForRequest(requestId, 1);
return responses[0] || null;
}
export async function create (patch = {}) {
if (!patch.parentId) {
throw new Error('New Response missing `parentId`');

View File

@@ -6,11 +6,12 @@
"longName": "Insomnia REST Client",
"description": "A simple and beautiful REST API client",
"homepage": "http://insomnia.rest",
"author": "Gregory Schier <support@insomnia.rest>",
"author": "Insomnia <support@insomnia.rest>",
"main": "main.js",
"dependencies": {
"analytics-node": "^2.1.0",
"classnames": "^2.2.3",
"clone": "^2.1.0",
"electron-context-menu": "^0.4.0",
"electron-squirrel-startup": "^1.0.0",
"hkdf": "0.0.2",
@@ -27,7 +28,6 @@
"request": "^2.71.0",
"srp-js": "0.2.0",
"tough-cookie": "^2.3.1",
"traverse": "^0.6.6",
"uuid": "^3.0.0",
"vkbeautify": "^0.99.1",
"whatwg-fetch": "^2.0.1",

View File

@@ -740,7 +740,7 @@ export async function getOrCreateAllActiveResources (resourceGroupId = null) {
activeResourceMap[doc._id] = await createResourceForDoc(doc);
created++;
} catch (e) {
logger.error(`Failed to create resource for ${doc._id}`, e, {doc});
logger.error(`Failed to create resource for ${doc._id} ${e}`, {doc});
}
}
}

View File

@@ -0,0 +1,26 @@
import BaseExtension from './base/BaseExtension';
export default class NowExtension extends BaseExtension {
constructor () {
super();
this.tags = ['now'];
}
run (context, format = 'iso-8601') {
format = typeof format === 'string' ? format.toLowerCase() : 'unknown';
const now = new Date();
switch (format) {
case 'millis':
case 'ms':
return now.getTime();
case 'unix':
case 'seconds':
case 's':
return Math.round(now.getTime() / 1000);
case 'iso-8601':
default:
return now.toISOString();
}
}
}

View File

@@ -0,0 +1,51 @@
import jq from 'jsonpath';
import * as models from '../../models';
import BaseExtension from './base/BaseExtension';
const TAG_NAME = 'JSONPath';
export default class ResponseJsonPathExtension extends BaseExtension {
constructor () {
super();
this.tags = [TAG_NAME];
}
async run (context, id, query) {
const request = await models.request.getById(id);
if (!request) {
throw new Error(`[${TAG_NAME}] Could not find request ${id}`)
}
const response = await models.response.getLatestForRequest(id);
if (!response) {
throw new Error(`[${TAG_NAME}] No responses for request ${id}`);
}
const bodyBuffer = new Buffer(response.body, response.encoding);
const bodyStr = bodyBuffer.toString();
let body;
try {
body = JSON.parse(bodyStr);
} catch (err) {
throw new Error(`[${TAG_NAME}] Invalid JSON: ${err.message}`)
}
let results;
try {
results = jq.query(body, query);
} catch (err) {
throw new Error(`[${TAG_NAME}] Invalid JSONPath query: ${query}`)
}
if (results.length === 0) {
throw new Error(`[${TAG_NAME}] Returned no results: ${query}`)
} else if (results.length > 1) {
throw new Error(`[${TAG_NAME}] Returned more than one result: ${query}`)
}
return `${results[0] || ''}`;
}
}

View File

@@ -0,0 +1,12 @@
import BaseExtension from './base/BaseExtension';
export default class TimestampExtension extends BaseExtension {
constructor () {
super();
this.tags = ['timestamp'];
}
run (context) {
return Date.now();
}
}

View File

@@ -0,0 +1,27 @@
import uuid from 'uuid';
import BaseExtension from './base/BaseExtension';
export default class UuidExtension extends BaseExtension {
constructor () {
super();
this.tags = ['uuid'];
}
run (context, version) {
if (typeof version === 'number') {
version += '';
} else if (typeof version === 'string') {
version = version.toLowerCase();
}
switch (version) {
case '1':
case 'v1':
return uuid.v1();
case '4':
case 'v4':
default:
return uuid.v4();
}
}
}

View File

@@ -0,0 +1,23 @@
import * as templating from '../../index';
function assertTemplate (txt, expected) {
return async function () {
const result = await templating.render(txt);
expect(result).toMatch(expected);
}
}
const isoRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/;
const secondsRe = /^\d{10}$/;
const millisRe = /^\d{13}$/;
describe('NowExtension', () => {
it('renders default ISO', assertTemplate('{% now %}', isoRe));
it('renders ISO-8601', assertTemplate('{% now "ISO-8601" %}', isoRe));
it('renders seconds', assertTemplate('{% now "seconds" %}', secondsRe));
it('renders s', assertTemplate('{% now "s" %}', secondsRe));
it('renders unix', assertTemplate('{% now "unix" %}', secondsRe));
it('renders millis', assertTemplate('{% now "millis" %}', millisRe));
it('renders ms', assertTemplate('{% now "ms" %}', millisRe));
it('renders default fallback', assertTemplate('{% now "foo" %}', isoRe));
});

View File

@@ -0,0 +1,86 @@
import * as templating from '../../index';
import * as db from '../../../common/database';
import * as models from '../../../models';
describe('ResponseJsonPathExtension', async () => {
beforeEach(() => db.init(models.types(), {inMemoryOnly: true}, true));
it('renders basic JSONPath query', async () => {
const request = await models.request.create({parentId: 'foo'});
await models.response.create({parentId: request._id, body: '{"foo": "bar"}'});
const result = await templating.render(`{% JSONPath "${request._id}", "$.foo" %}`);
expect(result).toBe('bar');
});
it('fails on invalid JSON', async () => {
const request = await models.request.create({parentId: 'foo'});
await models.response.create({parentId: request._id, body: '{"foo": "bar"'});
try {
await templating.render(`{% JSONPath "${request._id}", "$.foo" %}`);
fail('JSON should have failed to parse');
} catch (err) {
expect(err.message).toContain('Invalid JSON: Unexpected end of JSON input');
}
});
it('fails on no responses', async () => {
const request = await models.request.create({parentId: 'foo'});
try {
await templating.render(`{% JSONPath "${request._id}", "$.foo" %}`);
fail('JSON should have failed to parse');
} catch (err) {
expect(err.message).toContain('No responses for request');
}
});
it('fails on no request', async () => {
await models.response.create({parentId: 'req_test', body: '{"foo": "bar"}'});
try {
await templating.render(`{% JSONPath "req_test", "$.foo" %}`);
fail('JSON should have failed to parse')
} catch (err) {
expect(err.message).toContain('Could not find request req_test');
}
});
it('fails on invalid query', async () => {
const request = await models.request.create({parentId: 'foo'});
await models.response.create({parentId: request._id, body: '{"foo": "bar"}'});
try {
await templating.render(`{% JSONPath "${request._id}", "$$" %}`);
fail('JSON should have failed to parse')
} catch (err) {
expect(err.message).toContain('Invalid JSONPath query: $$');
}
});
it('fails on no results', async () => {
const request = await models.request.create({parentId: 'foo'});
await models.response.create({parentId: request._id, body: '{"foo": "bar"}'});
try {
await templating.render(`{% JSONPath "${request._id}", "$.missing" %}`);
fail('JSON should have failed to parse')
} catch (err) {
expect(err.message).toContain('Returned no results: $.missing');
}
});
it('fails on more than 1 result', async () => {
const request = await models.request.create({parentId: 'foo'});
await models.response.create({parentId: request._id, body: '{"array": [1,2,3]}'});
try {
await templating.render(`{% JSONPath "${request._id}", "$.array.*" %}`);
fail('JSON should have failed to parse')
} catch (err) {
expect(err.message).toContain('Returned more than one result: $.array.*');
}
});
});

View File

@@ -0,0 +1,14 @@
import * as templating from '../../index';
function assertTemplate (txt, expected) {
return async function () {
const result = await templating.render(txt);
expect(result).toMatch(expected);
}
}
const millisRe = /^\d{13}$/;
describe('TimestampExtension', () => {
it('renders basic', assertTemplate('{% timestamp %}', millisRe));
});

View File

@@ -0,0 +1,19 @@
import * as templating from '../../index';
function assertTemplate (txt, expected) {
return async function () {
const result = await templating.render(txt);
expect(result).toMatch(expected);
}
}
describe('UuidExtension', () => {
it('renders default v4', assertTemplate('{% uuid %}', 'dd2ccc1a-2745-477a-881a-9e8ef9d42403'));
it('renders 4', assertTemplate('{% uuid "4" %}', 'e3e96e5f-dd68-4229-8b66-dee1f0940f3d'));
it('renders 4 num', assertTemplate('{% uuid 4 %}', 'a262d22b-5fa8-491c-9bd9-58fba03e301e'));
it('renders v4', assertTemplate('{% uuid "v4" %}', '2e7c2688-09ee-44b8-900d-5cbbaa7d3a19'));
it('renders 1', assertTemplate('{% uuid "1" %}', 'f7272c80-f493-11e6-bc64-92361f002671'));
it('renders 1 num', assertTemplate('{% uuid 1 %}', 'f7272f0a-f493-11e6-bc64-92361f002671'));
it('renders v1', assertTemplate('{% uuid "v1" %}', 'f72733a6-f493-11e6-bc64-92361f002671'));
it('renders default fallback', assertTemplate('{% uuid "foo" %}', 'e7d698c4-c7d2-409c-90c6-22bcc94ba4ab'));
});

View File

@@ -0,0 +1,49 @@
export default class NowExtension {
constructor () {
// TODO: Subclass should set this
// this.tags = ['now'];
}
parse (parser, nodes, lexer) {
const tok = parser.nextToken();
let args;
if (parser.peekToken().type !== lexer.TOKEN_BLOCK_END) {
args = parser.parseSignature(null, true);
} else {
// Not sure why this is needed, but it fails without it
args = new nodes.NodeList(tok.lineno, tok.colno);
args.addChild(new nodes.Literal(0, 0, ""));
}
parser.advanceAfterBlockEnd(tok.value);
return new nodes.CallExtensionAsync(this, 'asyncRun', args);
}
asyncRun (...runArgs) {
// Pull the callback off the end
const callback = runArgs[runArgs.length - 1];
const args = runArgs.slice(0, runArgs.length - 1);
let result;
try {
result = this.run(...args);
} catch (err) {
// In case a synchronous render fails
callback(err);
return;
}
// If the result is a promise, resolve it async
if (result instanceof Promise) {
result.then(
r => callback(null, r),
err => callback(err)
);
return;
}
// If the result is not a Promise, return it synchronously
callback(null, result);
}
}

View File

@@ -0,0 +1,13 @@
import TimestampExtension from './TimestampExtension';
import UuidExtension from './UuidExtension';
import NowExtension from './NowExtension';
import ResponseJsonPathExtension from './ResponseJsonPathExtension';
export function all () {
return [
TimestampExtension,
UuidExtension,
NowExtension,
ResponseJsonPathExtension,
]
}

42
app/templating/index.js Normal file
View File

@@ -0,0 +1,42 @@
import nunjucks from 'nunjucks';
import * as extensions from './extensions';
/**
* Render text based on stuff
* @param {String} text - Nunjucks template in text form
* @param {Object} [config] - Config options for rendering
* @param {Object} [config.context] - Context to render with
*/
export async function render (text, config = {}) {
const context = config.context || {};
return new Promise((resolve, reject) => {
_getEnv().renderString(text, context, (err, result) => {
if (err) {
const sanitizedMsg = err.message.replace(/\(unknown path\)\s*/, '');
reject(new Error(sanitizedMsg));
} else {
resolve(result);
}
});
});
}
// ~~~~~~~~~~~~~ //
// Private Stuff //
// ~~~~~~~~~~~~~ //
let _nunjucksEnvironment = null;
function _getEnv () {
if (!_nunjucksEnvironment) {
_nunjucksEnvironment = nunjucks.configure({
autoescape: false,
});
for (const Cls of extensions.all()) {
_nunjucksEnvironment.addExtension(Cls.name, new Cls());
}
}
return _nunjucksEnvironment;
}

View File

@@ -4,7 +4,6 @@ import Link from './base/Link';
import * as fetch from '../../common/fetch';
import {trackEvent} from '../../analytics/index';
import * as models from '../../models/index';
import * as querystring from '../../common/querystring';
import * as constants from '../../common/constants';
import * as db from '../../common/database';

View File

@@ -167,7 +167,9 @@ class WorkspaceEnvironmentsEditModal extends Component {
<DropdownItem onClick={this._handleAddEnvironment} value={false}>
<i className="fa fa-eye"/> Environment
</DropdownItem>
<DropdownItem onClick={this._handleAddEnvironment} value={true}>
<DropdownItem onClick={this._handleAddEnvironment}
value={true}
title="Environment will not be exported or synced">
<i className="fa fa-eye-slash"/> Private Environment
</DropdownItem>
</Dropdown>
@@ -184,7 +186,7 @@ class WorkspaceEnvironmentsEditModal extends Component {
<button onClick={() => this._handleShowEnvironment(environment)}>
{environment.isPrivate ? (
<i className="fa fa-eye-slash faint"
title="Private Environment will not be exported or synced"
title="Environment will not be exported or synced"
/>
) : (
<i className="fa fa-blank faint"/>

View File

@@ -166,7 +166,7 @@ class App extends Component {
const {activeEnvironment, activeRequest} = this.props;
const environmentId = activeEnvironment ? activeEnvironment._id : null;
const context = await render.getRenderContext(activeRequest, environmentId);
return render.render(text, context);
return await render.render(text, context);
};
_handleGenerateCodeForActiveRequest = () => {

View File

@@ -15,12 +15,14 @@ environment:
# Things to install after repo clone
install:
- ps: Install-Product node $env:nodejs_version $env:Platform
- npm config set msvs_version 2015
- npm install -g npm@latest > Nul
- npm install > NUL
- npm config set msvs_version 2015
cache:
- '%APPDATA%\npm-cache'
- '%USERPROFILE%\.electron'
- 'node_modules'
#---------------------------------#
# tests configuration #

View File

@@ -42,9 +42,9 @@
"plugins": [
"transform-object-rest-spread",
"transform-class-properties",
"add-module-exports",
"transform-regenerator",
"transform-runtime"
"transform-runtime",
"add-module-exports"
],
"env": {
"development": {
@@ -100,6 +100,7 @@
"dependencies": {
"analytics-node": "^2.1.0",
"classnames": "^2.2.3",
"clone": "^2.1.0",
"codemirror": "^5.23.0",
"electron-context-menu": "^0.4.0",
"electron-squirrel-startup": "^1.0.0",
@@ -128,7 +129,6 @@
"reselect": "^2.5.4",
"srp-js": "0.2.0",
"tough-cookie": "^2.3.1",
"traverse": "^0.6.6",
"uuid": "^3.0.0",
"vkbeautify": "^0.99.1",
"whatwg-fetch": "^2.0.1",
@@ -136,20 +136,20 @@
"xpath": "^0.0.23"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
"babel-cli": "^6.23.0",
"babel-core": "^6.23.1",
"babel-jest": "^18.0.0",
"babel-loader": "^6.2.10",
"babel-loader": "^6.3.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-class-properties": "^6.19.0",
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-plugin-transform-regenerator": "^6.21.0",
"babel-plugin-transform-regenerator": "^6.22.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.20.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-es2017": "^6.22.0",
"babel-preset-react": "^6.5.0",
"babel-preset-react-hmre": "^1.1.1",
"babel-runtime": "^6.20.0",
"concurrently": "^2.0.0",
"cross-env": "^2.0.0",
"css-loader": "^0.23.1",