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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue