Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe), packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier (CLI app), vendor/zapier-platform, and research/zapier vendor corpus. Gate 0 structure checks pass. Product code and research are not yet wired.
This commit is contained in:
commit
b4150c8250
1364 changed files with 6814366 additions and 0 deletions
140
vendor/zapier-platform/packages/schema/test/functional-constraints/bufferedCreateConstraints.js
vendored
Normal file
140
vendor/zapier-platform/packages/schema/test/functional-constraints/bufferedCreateConstraints.js
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
const operation = {
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number', required: true },
|
||||
{ key: 'location', type: 'string' },
|
||||
],
|
||||
};
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('bufferedCreateConstraints', () => {
|
||||
it('should error on creates with both perform and buffer defined without performBuffer', () => {
|
||||
operation.perform = '$func$2$f$';
|
||||
operation.buffer = {
|
||||
groupedBy: ['orderId'],
|
||||
limit: 10,
|
||||
};
|
||||
delete operation.performBuffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(2);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.operation must contain property "performBuffer" because property "buffer" is present.',
|
||||
);
|
||||
results.errors[1].stack.should.eql(
|
||||
'instance.creates.foo.operation must not contain property "perform" because it is mutually exclusive with property "buffer".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error on creates with both perform and performBuffer defined without buffer', () => {
|
||||
operation.perform = '$func$2$f$';
|
||||
operation.performBuffer = '$func$2$f$';
|
||||
delete operation.buffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(2);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.operation must contain property "buffer" because property "performBuffer" is present.',
|
||||
);
|
||||
results.errors[1].stack.should.eql(
|
||||
'instance.creates.foo.operation must not contain property "perform" because it is mutually exclusive with property "performBuffer".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error on creates with buffer defined without perform and performBuffer', () => {
|
||||
operation.buffer = {
|
||||
groupedBy: ['orderId'],
|
||||
limit: 10,
|
||||
};
|
||||
delete operation.perform;
|
||||
delete operation.performBuffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.operation must contain property "performBuffer" because property "buffer" is present.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error on creates with buffer defined without perform and performBuffer, and with groupedBy using optional inputFields', () => {
|
||||
operation.buffer = {
|
||||
groupedBy: ['location'],
|
||||
limit: 10,
|
||||
};
|
||||
delete operation.perform;
|
||||
delete operation.performBuffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(2);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.operation must contain property "performBuffer" because property "buffer" is present.',
|
||||
);
|
||||
results.errors[1].stack.should.eql(
|
||||
'instance.creates.foo.operation.buffer.groupedBy[0] cannot use optional or non-existent inputField "location".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error on creates with performBuffer defined without buffer and perform', () => {
|
||||
operation.performBuffer = '$func$2$f$';
|
||||
delete operation.perform;
|
||||
delete operation.buffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.operation must contain property "buffer" because property "performBuffer" is present.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error on creates with none of perform, buffer, and performBuffer defined', () => {
|
||||
delete operation.buffer;
|
||||
delete operation.perform;
|
||||
delete operation.performBuffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.operation requires property "perform".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error on creates with both buffer and performBuffer defined without perform', () => {
|
||||
operation.performBuffer = '$func$2$f$';
|
||||
operation.buffer = {
|
||||
groupedBy: ['orderId'],
|
||||
limit: 10,
|
||||
};
|
||||
delete operation.perform;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error on creates with perform defined without buffer and performBuffer', () => {
|
||||
operation.perform = '$func$2$f$';
|
||||
delete operation.buffer;
|
||||
delete operation.performBuffer;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
82
vendor/zapier-platform/packages/schema/test/functional-constraints/deepNestedFields.js
vendored
Normal file
82
vendor/zapier-platform/packages/schema/test/functional-constraints/deepNestedFields.js
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('deepNestedFields', () => {
|
||||
it('should not error on fields nested one level deep', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error on fields nested more than one level deep', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{ key: 'some do not have children' },
|
||||
{
|
||||
key: 'product',
|
||||
children: [{ key: 'name', type: 'string' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.foo.inputFields[1] must not contain deeply nested child fields. One level max.',
|
||||
);
|
||||
});
|
||||
});
|
||||
446
vendor/zapier-platform/packages/schema/test/functional-constraints/inputFieldGroupsConstraints.js
vendored
Normal file
446
vendor/zapier-platform/packages/schema/test/functional-constraints/inputFieldGroupsConstraints.js
vendored
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('inputFieldGroupsConstraints', () => {
|
||||
const baseDefinition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '17.2.0',
|
||||
};
|
||||
|
||||
describe('Action Types Validation', () => {
|
||||
it('should pass when group fields reference valid inputFieldGroups in triggers', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
triggers: {
|
||||
test_trigger: {
|
||||
key: 'test_trigger',
|
||||
noun: 'Test',
|
||||
display: {
|
||||
label: 'Test Trigger',
|
||||
description: 'A test trigger',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{
|
||||
key: 'contact',
|
||||
label: 'Contact Information',
|
||||
emphasize: false,
|
||||
},
|
||||
{ key: 'address', label: 'Address Information' },
|
||||
],
|
||||
inputFields: [
|
||||
{ key: 'name', group: 'contact' },
|
||||
{ key: 'email', group: 'contact' },
|
||||
{ key: 'street', group: 'address' },
|
||||
{ key: 'city', group: 'address' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error when group reference does not exist in inputFieldGroups', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searches: {
|
||||
test_search: {
|
||||
key: 'test_search',
|
||||
noun: 'Test',
|
||||
display: {
|
||||
label: 'Test Search',
|
||||
description: 'A test search',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{ key: 'contact', label: 'Contact Information' },
|
||||
],
|
||||
inputFields: [
|
||||
{ key: 'name', group: 'contact' },
|
||||
{ key: 'email', group: 'nonexistent' }, // This group doesn't exist
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('invalidGroupReference');
|
||||
results.errors[0].message.should.match(
|
||||
/Group "nonexistent" is not defined in inputFieldGroups/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when groups are used in children fields', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
creates: {
|
||||
test_create: {
|
||||
key: 'test_create',
|
||||
noun: 'Test',
|
||||
display: {
|
||||
label: 'Test Create',
|
||||
description: 'A test create',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [{ key: 'items', label: 'Item Details' }],
|
||||
inputFields: [
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
group: 'items', // Groups not allowed in children
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('groupInChildren');
|
||||
results.errors[0].message.should.match(
|
||||
/Group fields are not allowed in children fields/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when inputFieldGroups has duplicate keys', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
triggers: {
|
||||
test_trigger: {
|
||||
key: 'test_trigger',
|
||||
noun: 'Test',
|
||||
display: {
|
||||
label: 'Test Trigger',
|
||||
description: 'A test trigger',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{ key: 'contact', label: 'Contact Information' },
|
||||
{ key: 'address', label: 'Address Information' },
|
||||
{ key: 'contact', label: 'Duplicate Contact' }, // Duplicate key
|
||||
],
|
||||
inputFields: [{ key: 'name', group: 'contact' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('duplicateGroupKey');
|
||||
results.errors[0].message.should.match(
|
||||
/Duplicate group key "contact" found in inputFieldGroups/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resources Validation', () => {
|
||||
it('should pass when resource methods have valid group references', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
resources: {
|
||||
contact: {
|
||||
key: 'contact',
|
||||
noun: 'Contact',
|
||||
list: {
|
||||
display: {
|
||||
label: 'List Contacts',
|
||||
description: 'List all contacts',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [{ key: 'filter', label: 'Filter Options' }],
|
||||
inputFields: [
|
||||
{ key: 'status', group: 'filter' },
|
||||
{ key: 'type', group: 'filter' },
|
||||
],
|
||||
},
|
||||
},
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Contact',
|
||||
description: 'Create a new contact',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{ key: 'personal', label: 'Personal Info' },
|
||||
{ key: 'business', label: 'Business Info' },
|
||||
],
|
||||
inputFields: [
|
||||
{ key: 'firstName', group: 'personal' },
|
||||
{ key: 'lastName', group: 'personal' },
|
||||
{ key: 'company', group: 'business' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error when resource method has invalid group reference', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
resources: {
|
||||
contact: {
|
||||
key: 'contact',
|
||||
noun: 'Contact',
|
||||
search: {
|
||||
display: {
|
||||
label: 'Search Contacts',
|
||||
description: 'Search for contacts',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{ key: 'criteria', label: 'Search Criteria' },
|
||||
],
|
||||
inputFields: [
|
||||
{ key: 'name', group: 'criteria' },
|
||||
{ key: 'email', group: 'invalid_group' }, // Invalid group reference
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('invalidGroupReference');
|
||||
results.errors[0].message.should.match(
|
||||
/Group "invalid_group" is not defined in inputFieldGroups/,
|
||||
);
|
||||
results.errors[0].property.should.equal(
|
||||
'instance.resources.contact.search.operation.inputFields[1].group',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when resource method has duplicate group keys', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
resources: {
|
||||
project: {
|
||||
key: 'project',
|
||||
noun: 'Project',
|
||||
get: {
|
||||
display: {
|
||||
label: 'Get Project',
|
||||
description: 'Get a project by ID',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{ key: 'options', label: 'Options' },
|
||||
{ key: 'settings', label: 'Settings' },
|
||||
{ key: 'options', label: 'Duplicate Options' }, // Duplicate key
|
||||
],
|
||||
inputFields: [{ key: 'includeArchived', group: 'options' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('duplicateGroupKey');
|
||||
results.errors[0].message.should.match(
|
||||
/Duplicate group key "options" found in inputFieldGroups/,
|
||||
);
|
||||
results.errors[0].property.should.equal(
|
||||
'instance.resources.project.get.operation.inputFieldGroups[2].key',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when resource method has groups in children fields', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
resources: {
|
||||
order: {
|
||||
key: 'order',
|
||||
noun: 'Order',
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Order',
|
||||
description: 'Create a new order',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [{ key: 'items', label: 'Order Items' }],
|
||||
inputFields: [
|
||||
{
|
||||
key: 'lineItems',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
group: 'items', // Groups not allowed in children
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('groupInChildren');
|
||||
results.errors[0].message.should.match(
|
||||
/Group fields are not allowed in children fields/,
|
||||
);
|
||||
results.errors[0].property.should.equal(
|
||||
'instance.resources.order.create.operation.inputFields[0].children[0].group',
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate multiple resource methods independently', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
resources: {
|
||||
user: {
|
||||
key: 'user',
|
||||
noun: 'User',
|
||||
list: {
|
||||
display: {
|
||||
label: 'List Users',
|
||||
description: 'List all users',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [{ key: 'filter', label: 'Filter Options' }],
|
||||
inputFields: [{ key: 'active', group: 'filter' }],
|
||||
},
|
||||
},
|
||||
search: {
|
||||
display: {
|
||||
label: 'Search Users',
|
||||
description: 'Search for users',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [
|
||||
{ key: 'criteria', label: 'Search Criteria' },
|
||||
],
|
||||
inputFields: [
|
||||
{ key: 'email', group: 'nonexistent' }, // Invalid reference
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].name.should.equal('invalidGroupReference');
|
||||
results.errors[0].property.should.equal(
|
||||
'instance.resources.user.search.operation.inputFields[0].group',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mixed Validation', () => {
|
||||
it('should validate both action types and resources together', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
triggers: {
|
||||
user_trigger: {
|
||||
key: 'user_trigger',
|
||||
noun: 'User',
|
||||
display: {
|
||||
label: 'User Trigger',
|
||||
description: 'Trigger on user events',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [{ key: 'filter', label: 'Filter' }],
|
||||
inputFields: [{ key: 'status', group: 'filter' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
resources: {
|
||||
contact: {
|
||||
key: 'contact',
|
||||
noun: 'Contact',
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Contact',
|
||||
description: 'Create a new contact',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFieldGroups: [{ key: 'personal', label: 'Personal Info' }],
|
||||
inputFields: [{ key: 'name', group: 'personal' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should handle empty inputFieldGroups correctly', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
triggers: {
|
||||
test_trigger: {
|
||||
key: 'test_trigger',
|
||||
noun: 'Test',
|
||||
display: {
|
||||
label: 'Test Trigger',
|
||||
description: 'A test trigger',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFields: [{ key: 'name' }, { key: 'email' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
resources: {
|
||||
item: {
|
||||
key: 'item',
|
||||
noun: 'Item',
|
||||
list: {
|
||||
display: {
|
||||
label: 'List Items',
|
||||
description: 'List all items',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFields: [{ key: 'category' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
73
vendor/zapier-platform/packages/schema/test/functional-constraints/labelWhenVisible.js
vendored
Normal file
73
vendor/zapier-platform/packages/schema/test/functional-constraints/labelWhenVisible.js
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
const should = require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('labelWhenVisible', () => {
|
||||
it('should not error when label is gone and action is hidden', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
hidden: true,
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error when label is missing and action is visible ', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
hidden: false,
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
const err = results.errors[0];
|
||||
should(err.codeLinks[0].includes('BasicDisplaySchema')).be.true();
|
||||
err.property.should.eql('instance.creates.foo.display');
|
||||
});
|
||||
|
||||
it('should error when label is alone when create is visible ', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'nea',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
});
|
||||
});
|
||||
43
vendor/zapier-platform/packages/schema/test/functional-constraints/matchingKeys.js
vendored
Normal file
43
vendor/zapier-platform/packages/schema/test/functional-constraints/matchingKeys.js
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('matchingKeys', () => {
|
||||
it("should error if the keys don't match", () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
triggers: {}, // this should be harmlessly skipped
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'bar', // this is different than above, which shouldn't validate
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
});
|
||||
});
|
||||
269
vendor/zapier-platform/packages/schema/test/functional-constraints/mutuallyExclusiveFields.js
vendored
Normal file
269
vendor/zapier-platform/packages/schema/test/functional-constraints/mutuallyExclusiveFields.js
vendored
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('mutuallyExclusiveFields', () => {
|
||||
it('should not error on fields not mutually exclusive', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error on fields that have children and list', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
},
|
||||
],
|
||||
list: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
"instance.creates.foo.inputFields[1] must not contain children and list, as they're mutually exclusive.",
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error on fields that have children and list when list is false', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
children: [
|
||||
{
|
||||
key: 'product',
|
||||
},
|
||||
],
|
||||
list: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error on fields that have list and dict', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number' },
|
||||
{
|
||||
key: 'line_items',
|
||||
dict: true,
|
||||
list: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
"instance.creates.foo.inputFields[1] must not contain dict and list, as they're mutually exclusive.",
|
||||
);
|
||||
});
|
||||
|
||||
it('should error on fields that have dynamic and dict', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'orderId',
|
||||
type: 'number',
|
||||
dynamic: 'foo.id.number',
|
||||
dict: true,
|
||||
},
|
||||
{
|
||||
key: 'line_items',
|
||||
list: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
"instance.creates.foo.inputFields[0] must not contain dynamic and dict, as they're mutually exclusive.",
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error on fields that have dynamic and dict when dict is false', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'orderId',
|
||||
type: 'number',
|
||||
dynamic: 'foo.id.number',
|
||||
dict: false,
|
||||
},
|
||||
{
|
||||
key: 'line_items',
|
||||
list: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error on fields that have dynamic and choices', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'orderId',
|
||||
type: 'number',
|
||||
dynamic: 'foo.id.number',
|
||||
choices: {
|
||||
uno: 1,
|
||||
dos: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'line_items',
|
||||
list: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
"instance.creates.foo.inputFields[0] must not contain dynamic and choices, as they're mutually exclusive.",
|
||||
);
|
||||
});
|
||||
});
|
||||
63
vendor/zapier-platform/packages/schema/test/functional-constraints/pollingThrottle.js
vendored
Normal file
63
vendor/zapier-platform/packages/schema/test/functional-constraints/pollingThrottle.js
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
const operation = {
|
||||
type: 'polling',
|
||||
perform: '$func$2$f$',
|
||||
throttle: {
|
||||
window: 60,
|
||||
limit: 1,
|
||||
scope: ['account'],
|
||||
},
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'orderId', type: 'number', required: true },
|
||||
{ key: 'location', type: 'string' },
|
||||
],
|
||||
};
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
triggers: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'New Foo',
|
||||
description: 'Triggers when a new foo is added.',
|
||||
},
|
||||
operation,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('pollingThrottle', () => {
|
||||
it('should error on a polling trigger with the "retry" field set in its throttle configuration', () => {
|
||||
operation.throttle.retry = false;
|
||||
let results;
|
||||
|
||||
// for polling trigger with operation.type set
|
||||
results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.triggers.foo.operation.throttle must not use the "retry" field for a polling trigger.',
|
||||
);
|
||||
|
||||
// for polling trigger with operation.type unset
|
||||
delete operation.type;
|
||||
results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.triggers.foo.operation.throttle must not use the "retry" field for a polling trigger.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error on a polling trigger without the "retry" field set in its throttle configuration', () => {
|
||||
delete operation.throttle.retry;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
257
vendor/zapier-platform/packages/schema/test/functional-constraints/searchAndCreatesAlias.js
vendored
Normal file
257
vendor/zapier-platform/packages/schema/test/functional-constraints/searchAndCreatesAlias.js
vendored
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('searchAndCreatesAlias', () => {
|
||||
it('should not error if schema contains both searchOrCreates and searchAndCreates keys', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
|
||||
creates: {
|
||||
add_product: {
|
||||
key: 'add_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Create Product',
|
||||
description: 'Creates a new Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$0$f$',
|
||||
sample: { id: 1, title: 'Air Jordan' },
|
||||
},
|
||||
},
|
||||
|
||||
update_product: {
|
||||
key: 'update_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Update Product',
|
||||
description: 'Updates an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1, title: 'Nike Dunk High Retro' },
|
||||
inputFields: [{ key: 'product_id', type: 'string' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
searches: {
|
||||
find_product: {
|
||||
key: 'find_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Search Product',
|
||||
description: 'Searches for an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$1$f$',
|
||||
inputFields: [{ key: 'product_title' }],
|
||||
outputFields: [{ key: 'product_title', type: 'string' }],
|
||||
sample: { id: 1, title: 'Nike Air' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Can have both searchOrCreates AND searchAndCreates defined
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
},
|
||||
},
|
||||
searchAndCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'id',
|
||||
},
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error if schema contains only searchOrCreates key', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
|
||||
creates: {
|
||||
add_product: {
|
||||
key: 'add_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Create Product',
|
||||
description: 'Creates a new Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$0$f$',
|
||||
sample: { id: 1, title: 'Air Jordan' },
|
||||
},
|
||||
},
|
||||
|
||||
update_product: {
|
||||
key: 'update_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Update Product',
|
||||
description: 'Updates an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1, title: 'Nike Dunk High Retro' },
|
||||
inputFields: [{ key: 'product_id', type: 'string' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
searches: {
|
||||
find_product: {
|
||||
key: 'find_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Search Product',
|
||||
description: 'Searches for an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$1$f$',
|
||||
inputFields: [{ key: 'product_title' }],
|
||||
outputFields: [{ key: 'product_title', type: 'string' }],
|
||||
sample: { id: 1, title: 'Nike Air' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
|
||||
search: 'find_product',
|
||||
|
||||
create: 'add_product',
|
||||
|
||||
update: 'update_product',
|
||||
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'id',
|
||||
},
|
||||
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error if schema contains only searchAndCreates key', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
|
||||
creates: {
|
||||
add_product: {
|
||||
key: 'add_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Create Product',
|
||||
description: 'Creates a new Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$0$f$',
|
||||
sample: { id: 1, title: 'Air Jordan' },
|
||||
},
|
||||
},
|
||||
|
||||
update_product: {
|
||||
key: 'update_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Update Product',
|
||||
description: 'Updates an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1, title: 'Nike Dunk High Retro' },
|
||||
inputFields: [{ key: 'product_id', type: 'string' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
searches: {
|
||||
find_product: {
|
||||
key: 'find_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Search Product',
|
||||
description: 'Searches for an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$1$f$',
|
||||
inputFields: [{ key: 'product_title' }],
|
||||
outputFields: [{ key: 'product_title', type: 'string' }],
|
||||
sample: { id: 1, title: 'Nike Air' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
searchAndCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
|
||||
search: 'find_product',
|
||||
|
||||
create: 'add_product',
|
||||
|
||||
update: 'update_product',
|
||||
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'id',
|
||||
},
|
||||
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
553
vendor/zapier-platform/packages/schema/test/functional-constraints/searchOrCreateKeys.js
vendored
Normal file
553
vendor/zapier-platform/packages/schema/test/functional-constraints/searchOrCreateKeys.js
vendored
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const _ = require('lodash');
|
||||
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('searchOrCreateKeys', () => {
|
||||
const baseDefinition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
|
||||
creates: {
|
||||
add_product: {
|
||||
key: 'add_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Create Product',
|
||||
description: 'Creates a new Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$0$f$',
|
||||
sample: { id: 1, title: 'Air Jordan' },
|
||||
},
|
||||
},
|
||||
|
||||
update_product: {
|
||||
key: 'update_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Update Product',
|
||||
description: 'Updates an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1, title: 'Nike Dunk High Retro' },
|
||||
inputFields: [{ key: 'product_id', type: 'string' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
searches: {
|
||||
find_product: {
|
||||
key: 'find_product',
|
||||
noun: 'Product',
|
||||
display: {
|
||||
label: 'Search Product',
|
||||
description: 'Searches for an existing Product',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$1$f$',
|
||||
inputFields: [{ key: 'product_title', type: 'string' }],
|
||||
sample: { id: 1, title: 'Nike Air' },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('should not error on properly defined searchesOrCreates', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error on properly defined searchesOrCreates with update key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'id',
|
||||
},
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error if searchesAndCreates contains an unknown key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchAndCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
test: 'wrong', // Unknown key
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchAndCreates.findOrCreateProduct is not allowed to have the additional property "test"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchOrCreate.key does not match a search key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'search_product', // Different key from any searches.key
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.key must match a "key" from a search (options: find_product)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchAndCreate.key does not match a search key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchAndCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'search_product', // Different key from any searches.key
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchAndCreates.findOrCreateProduct.key must match a "key" from a search (options: find_product)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchesOrCreate.create key not matching an existing create key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'create_product', // No such key in creates
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.create must match a "key" from a create (options: add_product,update_product)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchesAndCreate.create key not matching an existing create key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchAndCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'create_product', // No such key in creates
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchAndCreates.findOrCreateProduct.create must match a "key" from a create (options: add_product,update_product)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchesOrCreate.update is defined and does not match a create key', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'create_product', // No such key in creates
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.update must match a "key" from a create (options: add_product,update_product)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchesOrCreate.updateInputFromSearchOutput is defined and but searchesOrCreate.update is not defined', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.updateInputFromSearchOutput requires searchOrCreates.findOrCreateProduct.update to be defined',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if searchesOrCreate.searchUniqueInputToOutputConstraint is defined and but searchesOrCreate.update is not defined', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.searchUniqueInputToOutputConstraint requires searchOrCreates.findOrCreateProduct.update to be defined',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if the searchOrCreate.updateInputFromSearchOutput object has a key not in creates.operations.inputFields', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
updateInputFromSearchOutput: {
|
||||
productId: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.updateInputFromSearchOutput object key must match a "key" from a creates.update_product.operation.inputFields (options: product_id)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if the searchOrCreate.updateInputFromSearchOutput object has a value not in searches.operation.outputFields or searches.operation.sample, when these are defined', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'product_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.updateInputFromSearchOutput object value must match a "key" from searches.find_product.operation.(outputFields|sample) (options: id,title)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error if the searchOrCreate.updateInputFromSearchOutput object has a value but searches.operation.outputFields and searches.operation.sample are not defined', () => {
|
||||
const definition = _.cloneDeep(baseDefinition);
|
||||
definition.searchOrCreates = {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
updateInputFromSearchOutput: {
|
||||
product_id: 'id',
|
||||
},
|
||||
},
|
||||
};
|
||||
delete definition.searches.find_product.operation.sample;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error if the searchOrCreate.searchUniqueInputToOutputConstraint object has a key but searches.operations.inputFields is not defined', () => {
|
||||
const definition = _.cloneDeep(baseDefinition);
|
||||
definition.searchOrCreates = {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'title',
|
||||
},
|
||||
},
|
||||
};
|
||||
definition.searches.find_product.operation.inputFields = [];
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.searchUniqueInputToOutputConstraint object key must match a "key" from a searches.find_product.operation.inputFields (no "key" found in inputFields)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if the searchOrCreate.searchUniqueInputToOutputConstraint object has a key not in searches.operations.inputFields', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
// title_of_product does not exist in searches.operation.(outputFields.key|sample keys)
|
||||
title_of_product: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.searchUniqueInputToOutputConstraint object key must match a "key" from a searches.find_product.operation.inputFields (options: product_title)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error if the searchOrCreate.searchUniqueInputToOutputConstraint object has a value not in searches.operation.outputFields or searches.operation.sample, when these are defined', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'product_title',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.findOrCreateProduct.searchUniqueInputToOutputConstraint object value must match a "key" from searches.find_product.operation.(outputFields|sample) (options: id,title)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error if the searchOrCreate.searchUniqueInputToOutputConstraint object has a value but searches.operation.outputFields and searches.operation.sample are not defined', () => {
|
||||
const definition = _.cloneDeep(baseDefinition);
|
||||
definition.searchOrCreates = {
|
||||
findOrCreateProduct: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Search or Create',
|
||||
description:
|
||||
'Tries to fetch a product, creates one if it does not find at least one.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: 'product_title',
|
||||
},
|
||||
},
|
||||
};
|
||||
delete definition.searches.find_product.operation.sample;
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should only give type errors if searchOrCreate.updateInputFromSearchOutput and searchOrCreate.searchUniqueInputToOutputConstraint are not objects', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
find_product: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Find, Create, or Update Product',
|
||||
description: 'Finds, creates, or updates a product.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
updateInputFromSearchOutput: 'a_string',
|
||||
searchUniqueInputToOutputConstraint: ['title'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
|
||||
// If updateInputFromSearchOutput or searchUniqueInputToOutputConstraint has
|
||||
// the wrong type, we don't want to validate its content.
|
||||
results.errors.should.have.length(2);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.find_product.updateInputFromSearchOutput is not of a type(s) object',
|
||||
);
|
||||
results.errors[1].stack.should.eql(
|
||||
'instance.searchOrCreates.find_product.searchUniqueInputToOutputConstraint is not of a type(s) object',
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip searchUniqueInputToOutputConstraint value check if it is nested', () => {
|
||||
const definition = {
|
||||
...baseDefinition,
|
||||
searchOrCreates: {
|
||||
find_product: {
|
||||
key: 'find_product',
|
||||
display: {
|
||||
label: 'Find, Create, or Update Product',
|
||||
description: 'Finds, creates, or updates a product.',
|
||||
},
|
||||
search: 'find_product',
|
||||
create: 'add_product',
|
||||
update: 'update_product',
|
||||
searchUniqueInputToOutputConstraint: {
|
||||
product_title: {
|
||||
value_from_need: 'lookup_value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
151
vendor/zapier-platform/packages/schema/test/functional-constraints/uniqueInputFieldKeys.js
vendored
Normal file
151
vendor/zapier-platform/packages/schema/test/functional-constraints/uniqueInputFieldKeys.js
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
describe('uniqueInputFieldKeys', () => {
|
||||
it('should error when fields are repeated', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [{ key: 'name' }, { key: 'name' }, { key: 'name' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(2);
|
||||
|
||||
// all errors reference the first instance of `name`
|
||||
results.errors
|
||||
.every(
|
||||
(err) =>
|
||||
err.message ===
|
||||
'inputField keys must be unique for each action. The key "name" is already in use at creates.foo.operation.inputFields[0].key',
|
||||
)
|
||||
.should.be.true();
|
||||
});
|
||||
|
||||
it('should error when fields nested fields are repeated', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'kids',
|
||||
children: [
|
||||
{ key: 'whatever' },
|
||||
{ key: 'name', type: 'number' },
|
||||
{ key: 'name', type: 'number' },
|
||||
],
|
||||
},
|
||||
{ key: 'name', type: 'number' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(2);
|
||||
|
||||
results.errors[0].message.should.eql(
|
||||
`inputField keys must be unique for each action, even if they're children. The key "name" is already in use at creates.foo.operation.inputFields[0].children[1].key`,
|
||||
);
|
||||
results.errors[1].message.should.eql(
|
||||
`inputField keys must be unique for each action. The key "name" is already in use at creates.foo.operation.inputFields[0].children[1].key`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when cousin fields are repeated', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{ key: 'x', children: [{ key: 'name' }] },
|
||||
{ key: 'y', children: [{ key: 'name' }] },
|
||||
{ key: 'z', children: [{ key: 'name' }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(2);
|
||||
|
||||
// all errors reference the first instance of `name`
|
||||
results.errors
|
||||
.every(
|
||||
(err) =>
|
||||
err.message ===
|
||||
`inputField keys must be unique for each action, even if they're children. The key "name" is already in use at creates.foo.operation.inputFields[0].children[0].key`,
|
||||
)
|
||||
.should.be.true();
|
||||
});
|
||||
|
||||
it('should handle non-inputField objects', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
// each of these is valid, but none declares a .key property
|
||||
'$func$2$f$', // dynamic fields
|
||||
'$func$2$f$',
|
||||
{ source: 'return []' },
|
||||
{ require: './some/js/file.js' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
614
vendor/zapier-platform/packages/schema/test/functional-constraints/validateJsonFieldSchema.js
vendored
Normal file
614
vendor/zapier-platform/packages/schema/test/functional-constraints/validateJsonFieldSchema.js
vendored
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const schema = require('../../schema');
|
||||
|
||||
// Helper to create a minimal app definition with a single json input field
|
||||
const makeDefinition = (fieldSchema) => ({
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'payload',
|
||||
type: 'json',
|
||||
schema: fieldSchema,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('validateJsonFieldSchema', () => {
|
||||
describe('standard action validation', () => {
|
||||
it('should not error when type is json and schema is provided', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error when type is json without schema', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'payload',
|
||||
type: 'json',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error when schema is provided without type json', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'payload',
|
||||
type: 'string',
|
||||
schema: { type: 'object' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'must have `type` set to `json` when `schema` is provided',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSON Schema validation', () => {
|
||||
it('should not error for a valid JSON Schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'integer' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for an empty schema object', () => {
|
||||
const results = schema.validateAppDefinition(makeDefinition({}));
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a schema with type as an array of object and array', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: ['object', 'array'],
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a schema using allOf/anyOf/oneOf/not', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
allOf: [
|
||||
{ type: 'object' },
|
||||
{
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
not: { type: 'array' },
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error for an invalid type in a nested property', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'not-a-type' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql('invalid type "not-a-type"');
|
||||
});
|
||||
|
||||
it('should error when required is not an array', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
required: 'name',
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql('required: must be an array');
|
||||
});
|
||||
|
||||
it('should error when properties contains a non-object value', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: 'should be an object',
|
||||
},
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'must be a valid JSON Schema object',
|
||||
);
|
||||
});
|
||||
|
||||
it('should report multiple errors for multiple invalid fields', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
required: 'not-array',
|
||||
properties: {
|
||||
name: { type: 'not-a-type' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(2);
|
||||
});
|
||||
|
||||
it('should error for invalid items schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'array',
|
||||
items: 'not-a-schema',
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'must be a valid JSON Schema object',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when root schema type is a primitive string', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({ type: 'string' }),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'root `type` must be "object" or "array"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when root schema type is an array of primitives', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({ type: ['string', 'null'] }),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'root `type` must be "object" or "array"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error for invalid enum value', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
enum: 'not-an-array',
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql('enum: must be an array');
|
||||
});
|
||||
|
||||
it('should validate schemas in resources', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
resources: {
|
||||
contact: {
|
||||
key: 'contact',
|
||||
noun: 'Contact',
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Contact',
|
||||
description: 'Create a new contact',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFields: [
|
||||
{
|
||||
key: 'payload',
|
||||
type: 'json',
|
||||
schema: { type: 'badtype' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'root `type` must be "object" or "array"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('children field validation', () => {
|
||||
it('should not error for valid schema in a child field', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'parent',
|
||||
children: [
|
||||
{
|
||||
key: 'nested_json',
|
||||
type: 'json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error for invalid schema in a child field', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'parent',
|
||||
children: [
|
||||
{
|
||||
key: 'nested_json',
|
||||
type: 'json',
|
||||
schema: { type: 'badtype' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'root `type` must be "object" or "array"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error for schema without type json in a child field', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
creates: {
|
||||
foo: {
|
||||
key: 'foo',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'parent',
|
||||
children: [
|
||||
{
|
||||
key: 'nested_json',
|
||||
type: 'string',
|
||||
schema: { type: 'object' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'must have `type` set to `json` when `schema` is provided',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resource validation', () => {
|
||||
it('should not error when type is json and schema is provided', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
resources: {
|
||||
contact: {
|
||||
key: 'contact',
|
||||
noun: 'Contact',
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Contact',
|
||||
description: 'Create a new contact',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFields: [
|
||||
{
|
||||
key: 'payload',
|
||||
type: 'json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error when schema is provided without type json', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
resources: {
|
||||
contact: {
|
||||
key: 'contact',
|
||||
noun: 'Contact',
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Contact',
|
||||
description: 'Create a new contact',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
inputFields: [
|
||||
{
|
||||
key: 'payload',
|
||||
type: 'string',
|
||||
schema: { type: 'object' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'must have `type` set to `json` when `schema` is provided',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$schema draft version support', () => {
|
||||
it('should not error for a schema with no $schema (defaults to Draft 7)', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a schema with Draft 4 $schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'http://json-schema.org/draft-04/schema#',
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a schema with Draft 6 $schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'http://json-schema.org/draft-06/schema#',
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a schema with Draft 7 $schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a Draft 7 $schema without trailing #', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'http://json-schema.org/draft-07/schema',
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should not error for a Draft 7 schema using exclusiveMinimum as a number', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'https://json-schema.org/draft-07/schema#',
|
||||
type: 'object',
|
||||
properties: {
|
||||
age: { type: 'integer', exclusiveMinimum: 5 },
|
||||
},
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should error for a Draft 4 schema using exclusiveMinimum as a number', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'https://json-schema.org/draft-04/schema#',
|
||||
type: 'object',
|
||||
properties: {
|
||||
age: { type: 'integer', exclusiveMinimum: 5 },
|
||||
},
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(2);
|
||||
results.errors[0].stack.should.containEql('exclusiveMinimum');
|
||||
results.errors[0].stack.should.containEql('is not of a type(s) boolean');
|
||||
results.errors[1].stack.should.containEql('minimum not found');
|
||||
});
|
||||
|
||||
it('should error for unsupported Draft 2019-09 $schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'https://json-schema.org/draft/2019-09/schema',
|
||||
type: 'object',
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'unsupported JSON Schema version',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error for unsupported Draft 2020-12 $schema', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
type: 'object',
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'unsupported JSON Schema version',
|
||||
);
|
||||
});
|
||||
|
||||
it('should error for an unrecognized $schema URI', () => {
|
||||
const results = schema.validateAppDefinition(
|
||||
makeDefinition({
|
||||
$schema: 'https://example.com/my-custom-schema',
|
||||
type: 'object',
|
||||
}),
|
||||
);
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.containEql(
|
||||
'unsupported JSON Schema version',
|
||||
);
|
||||
results.errors[0].stack.should.containEql('Supported versions');
|
||||
});
|
||||
});
|
||||
});
|
||||
412
vendor/zapier-platform/packages/schema/test/index.js
vendored
Normal file
412
vendor/zapier-platform/packages/schema/test/index.js
vendored
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
'use strict';
|
||||
|
||||
const should = require('should');
|
||||
const schema = require('../schema');
|
||||
|
||||
const testUtils = require('./utils');
|
||||
|
||||
const appDefinition = require('../examples/definition.json');
|
||||
|
||||
const copy = (o) => JSON.parse(JSON.stringify(o));
|
||||
|
||||
const NUM_SCHEMAS = 65; // changes regularly as we expand
|
||||
|
||||
describe('app', () => {
|
||||
describe('validation', () => {
|
||||
it('should be a valid app', () => {
|
||||
const results = schema.validateAppDefinition(appDefinition);
|
||||
results.errors.should.eql([]);
|
||||
});
|
||||
|
||||
it('should invalidate deep errors', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
delete appCopy.version;
|
||||
delete appCopy.triggers.contact_by_tag.noun;
|
||||
delete appCopy.triggers.contact_by_tag.display.label;
|
||||
appCopy.triggers.contact_by_tag.operation.inputFields[0].type = 'loltype';
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
results.errors.length.should.eql(4);
|
||||
});
|
||||
|
||||
it('should invalidate a badly named trigger', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
appCopy.triggers['3contact_by_tag'] = appCopy.triggers.contact_by_tag;
|
||||
delete appCopy.triggers.contact_by_tag;
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.triggers is not allowed to have the additional property "3contact_by_tag"',
|
||||
);
|
||||
results.errors.length.should.eql(2); // additional property error + top-level key doesn't match trigger key
|
||||
});
|
||||
|
||||
it('should invalidate a badly named create', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
appCopy.creates['3contact_by_tag'] = appCopy.creates.tag_create;
|
||||
delete appCopy.creates.tag_create;
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates is not allowed to have the additional property "3contact_by_tag"',
|
||||
);
|
||||
results.errors.length.should.eql(2); // additional property error + top-level key doesn't match create key
|
||||
});
|
||||
|
||||
it('should invalidate a create with a bad key', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
appCopy.creates.tag_create.key = 'tag:create';
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.creates.tag_create.key does not match pattern "^[a-zA-Z]+[a-zA-Z0-9_]*$"',
|
||||
);
|
||||
results.errors.length.should.eql(2); // invalid name and top-level key doesn't match create key
|
||||
});
|
||||
|
||||
it('should run and pass functional constraints', function () {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
searches: {
|
||||
fooSearch: {
|
||||
key: 'fooSearch',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Find Foo',
|
||||
description: 'Find a foo...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
creates: {
|
||||
fooCreate: {
|
||||
key: 'fooCreate',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
searchOrCreates: {
|
||||
fooSearchOrCreate: {
|
||||
key: 'fooSearch',
|
||||
display: {
|
||||
label: 'Find or Create a...',
|
||||
description: 'Something Something',
|
||||
},
|
||||
search: 'fooSearch',
|
||||
create: 'fooCreate',
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should run functional constraints with errors', function () {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
searches: {
|
||||
fooSearch: {
|
||||
key: 'fooSearch',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Find Foo',
|
||||
description: 'Find a foo...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
creates: {
|
||||
fooCreate: {
|
||||
key: 'fooCreate',
|
||||
noun: 'Foo',
|
||||
display: {
|
||||
label: 'Create Foo',
|
||||
description: 'Creates a...',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
searchOrCreates: {
|
||||
fooSearchOrCreate: {
|
||||
key: 'fooSearchOrCreate',
|
||||
display: {
|
||||
label: 'Find or Create a...',
|
||||
description: 'Something Something',
|
||||
},
|
||||
search: 'fooBad',
|
||||
create: 'fooBad',
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(3);
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.searchOrCreates.fooSearchOrCreate.key must match a "key" from a search (options: fooSearch)',
|
||||
);
|
||||
results.errors[1].stack.should.eql(
|
||||
'instance.searchOrCreates.fooSearchOrCreate.search must match a "key" from a search (options: fooSearch)',
|
||||
);
|
||||
results.errors[2].stack.should.eql(
|
||||
'instance.searchOrCreates.fooSearchOrCreate.create must match a "key" from a create (options: fooCreate)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate inputFormat', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
appCopy.authentication = {
|
||||
type: 'custom',
|
||||
test: {
|
||||
url: 'https://example.com',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
key: 'subdomain',
|
||||
type: 'string',
|
||||
required: true,
|
||||
inputFormat: 'https://{{input}}.example.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
// this line ensures we're getting a ValidatorResult class, not just an object that looks like one
|
||||
should(results.valid).eql(true);
|
||||
results.errors.should.eql([]);
|
||||
});
|
||||
|
||||
it('should invalidate illegal inputFormat', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
appCopy.authentication = {
|
||||
type: 'custom',
|
||||
test: {
|
||||
url: 'https://example.com',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
key: 'subdomain',
|
||||
type: 'string',
|
||||
required: true,
|
||||
inputFormat: 'https://{{input}.example.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
results.errors.length.should.eql(1);
|
||||
should(results.valid).eql(false);
|
||||
|
||||
const error = results.errors[0];
|
||||
error.name.should.eql('pattern');
|
||||
error.instance.should.eql('https://{{input}.example.com');
|
||||
});
|
||||
|
||||
it('should validate legacy properties', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
appCopy.legacy = {
|
||||
subscribeUrl: 'https://example.com',
|
||||
triggers: {
|
||||
contact: {
|
||||
url: 'https://example.com',
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
results.errors.should.eql([]);
|
||||
});
|
||||
|
||||
it('should validate safe/unsafe auth fields', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
|
||||
// Set up a "custom" auth that has two fields:
|
||||
// - "username" which is safe
|
||||
// - "password" which is not safe
|
||||
appCopy.authentication = {
|
||||
type: 'custom',
|
||||
test: {
|
||||
url: 'https://example.com',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
key: 'username',
|
||||
type: 'string',
|
||||
isNoSecret: true, // allowed
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
type: 'password',
|
||||
isNoSecret: false, // password is not safe
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
|
||||
// Expect zero errors
|
||||
results.errors.should.have.length(0);
|
||||
should(results.valid).eql(true);
|
||||
});
|
||||
|
||||
it('should invalidate a "safe" password field', () => {
|
||||
const appCopy = copy(appDefinition);
|
||||
|
||||
// Same "custom" auth but now marking "password" as isNoSecret=true
|
||||
appCopy.authentication = {
|
||||
type: 'custom',
|
||||
test: {
|
||||
url: 'https://example.com',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
key: 'password',
|
||||
type: 'string',
|
||||
isNoSecret: true, // invalid: password cannot be safe
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const results = schema.validateAppDefinition(appCopy);
|
||||
|
||||
// Expect at least one error because "password" can't have isNoSecret = true
|
||||
results.errors.should.have.length(1);
|
||||
should(results.valid).eql(false);
|
||||
|
||||
const [error] = results.errors;
|
||||
error.name.should.equal('sensitive');
|
||||
error.stack.should.eql(
|
||||
'instance.authentication.fields[0] cannot set isNoSecret as true for the sensitive key "password".',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('export', () => {
|
||||
it('should export the full schema', () => {
|
||||
const exportedSchema = schema.exportSchema();
|
||||
Object.keys(exportedSchema.schemas).length.should.eql(NUM_SCHEMAS);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamic choices disambiguation', () => {
|
||||
const FieldChoicesSchema = require('../lib/schemas/FieldChoicesSchema');
|
||||
const FieldDynamicChoicesSchema = require('../lib/schemas/FieldDynamicChoicesSchema');
|
||||
|
||||
it('FieldChoicesSchema should reject objects with a perform key', () => {
|
||||
const { errors } = FieldChoicesSchema.validate({ perform: '$func$0$f$' });
|
||||
errors.should.not.have.length(0);
|
||||
});
|
||||
|
||||
it('FieldDynamicChoicesSchema should accept objects with a perform key', () => {
|
||||
const { errors } = FieldDynamicChoicesSchema.validate({
|
||||
perform: '$func$0$f$',
|
||||
});
|
||||
errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('FieldChoicesSchema should still accept static object choices', () => {
|
||||
const { errors } = FieldChoicesSchema.validate({
|
||||
high: 'High',
|
||||
medium: 'Medium',
|
||||
low: 'Low',
|
||||
});
|
||||
errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('FieldChoicesSchema should still accept static array choices', () => {
|
||||
const { errors } = FieldChoicesSchema.validate(['open', 'closed']);
|
||||
errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should validate an app with dynamic choices on an input field', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
triggers: {
|
||||
newThing: {
|
||||
key: 'newThing',
|
||||
noun: 'Thing',
|
||||
display: {
|
||||
label: 'New Thing',
|
||||
description: 'Triggers on new things.',
|
||||
},
|
||||
operation: {
|
||||
type: 'polling',
|
||||
perform: '$func$0$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'org',
|
||||
label: 'Organization',
|
||||
helpText: 'Pick one.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
choices: { perform: '$func$1$f$' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
|
||||
it('should validate an app with both static and dynamic choices', () => {
|
||||
const definition = {
|
||||
version: '1.0.0',
|
||||
platformVersion: '1.0.0',
|
||||
triggers: {
|
||||
newThing: {
|
||||
key: 'newThing',
|
||||
noun: 'Thing',
|
||||
display: {
|
||||
label: 'New Thing',
|
||||
description: 'Triggers on new things.',
|
||||
},
|
||||
operation: {
|
||||
type: 'polling',
|
||||
perform: '$func$0$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'org',
|
||||
choices: { perform: '$func$1$f$' },
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
choices: { high: 'High', low: 'Low' },
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
choices: ['open', 'closed'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = schema.validateAppDefinition(definition);
|
||||
results.errors.should.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('auto test', () => {
|
||||
const _exportedSchema = schema.exportSchema();
|
||||
Object.keys(_exportedSchema.schemas).map((id) =>
|
||||
testUtils.testInlineSchemaExamples(id),
|
||||
);
|
||||
});
|
||||
154
vendor/zapier-platform/packages/schema/test/readability.js
vendored
Normal file
154
vendor/zapier-platform/packages/schema/test/readability.js
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
'use strict';
|
||||
|
||||
const should = require('should');
|
||||
|
||||
const AuthenticationSchema = require('../lib/schemas/AuthenticationSchema');
|
||||
const CreateSchema = require('../lib/schemas/CreateSchema');
|
||||
const TriggerSchema = require('../lib/schemas/TriggerSchema');
|
||||
|
||||
describe('readability', () => {
|
||||
it('should have decent messages for anyOf mismatches', () => {
|
||||
const results = AuthenticationSchema.validate({
|
||||
type: 'oauth2',
|
||||
test: 'whateverfake!',
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].stack.should.eql('instance is not of a type(s) object');
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
});
|
||||
|
||||
it('should have decent messages for minimum length not met', () => {
|
||||
const results = TriggerSchema.validate({
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
display: {
|
||||
label: '',
|
||||
description: 'Creates a new recipe.',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
},
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
results.errors[0].stack.should.eql(
|
||||
'instance.display.label does not meet minimum length of 2',
|
||||
);
|
||||
});
|
||||
|
||||
it('should have decent messages for value type mismatch', () => {
|
||||
const results = CreateSchema.validate({
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
display: {
|
||||
label: 'Create Recipe',
|
||||
description: 'Creates a new recipe.',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [123],
|
||||
},
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
results.errors[0].stack.should.eql('instance is not of a type(s) object');
|
||||
});
|
||||
|
||||
it('should handle falsy values for objects', () => {
|
||||
const results = CreateSchema.validate({
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
display: {
|
||||
label: 'Create Recipe',
|
||||
description: 'Creates a new recipe.',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [0],
|
||||
},
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
results.errors[0].stack.should.eql('instance is not of a type(s) object');
|
||||
});
|
||||
|
||||
it('should surface deep issues', () => {
|
||||
const results = CreateSchema.validate({
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
display: {
|
||||
label: 'Create Recipe',
|
||||
description: 'Creates a new recipe.',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [{ key: 'field', type: 'string', default: '' }],
|
||||
},
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
results.errors[0].property.should.eql(
|
||||
'instance.operation.inputFields[0].default',
|
||||
);
|
||||
results.errors[0].message.should.eql('does not meet minimum length of 1');
|
||||
});
|
||||
|
||||
it('should correctly surface subschema types', () => {
|
||||
const results = CreateSchema.validate({
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
display: {
|
||||
label: 'Create Recipe',
|
||||
description: 'Creates a new recipe.',
|
||||
},
|
||||
operation: {
|
||||
perform: {
|
||||
url: 'https://example.com',
|
||||
body: 123,
|
||||
},
|
||||
sample: { id: 1 },
|
||||
},
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].property.should.eql('instance.operation.perform.body');
|
||||
should(
|
||||
results.errors[0].message.includes('null,string,object,array'),
|
||||
).be.true();
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
results.errors[0].docLinks.length.should.eql(0);
|
||||
});
|
||||
|
||||
it('should be helpful for fieldChoices', () => {
|
||||
const results = CreateSchema.validate({
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
display: {
|
||||
label: 'Create Recipe',
|
||||
description: 'Creates a new recipe.',
|
||||
},
|
||||
operation: {
|
||||
perform: '$func$2$f$',
|
||||
sample: { id: 1 },
|
||||
inputFields: [
|
||||
{
|
||||
key: 'adsf',
|
||||
// schema says these should be strings
|
||||
choices: [1, 2, 3],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
results.errors.should.have.length(1);
|
||||
results.errors[0].property.should.eql(
|
||||
'instance.operation.inputFields[0].choices',
|
||||
);
|
||||
should(
|
||||
results.errors[0].docLinks[0].includes('schema.md#fieldchoicesschema'),
|
||||
).be.true();
|
||||
should(results.errors[0].property.endsWith('instance')).be.false();
|
||||
});
|
||||
});
|
||||
46
vendor/zapier-platform/packages/schema/test/utils.js
vendored
Normal file
46
vendor/zapier-platform/packages/schema/test/utils.js
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
const { SKIP_KEY } = require('../lib/constants');
|
||||
|
||||
const testInlineSchemaExamples = (name) => {
|
||||
const Schema = require('../lib/schemas/' + name);
|
||||
const goods = Schema.schema.examples || [];
|
||||
const bads = Schema.schema.antiExamples || [];
|
||||
|
||||
describe(name, () => {
|
||||
it('valid example schemas should pass validation', function () {
|
||||
if (!goods.length) {
|
||||
this.skip();
|
||||
} else {
|
||||
goods
|
||||
.filter((t) => !t[SKIP_KEY])
|
||||
.forEach((good) => {
|
||||
const { errors } = Schema.validate(good);
|
||||
errors.should.have.length(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('invalid example schemas should fail validation', function () {
|
||||
if (!bads.length) {
|
||||
this.skip();
|
||||
} else {
|
||||
bads
|
||||
.filter((t) => !t[SKIP_KEY])
|
||||
.map((t) => t.example)
|
||||
.forEach((bad) => {
|
||||
const { errors } = Schema.validate(bad);
|
||||
if (errors.length === 0) {
|
||||
console.log(bad);
|
||||
}
|
||||
errors.should.not.have.length(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
testInlineSchemaExamples,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue