Skip to content

Commit

Permalink
Merge branch 'release-15.8.0' into PDE-4991
Browse files Browse the repository at this point in the history
  • Loading branch information
kola-er committed May 15, 2024
2 parents 106a97b + b02b238 commit 6be0d3d
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 75 deletions.
2 changes: 1 addition & 1 deletion packages/core/integration-test/integration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ const doTest = (runner) => {
it('should log requests', () => {
const event = {
command: 'execute',
method: 'resources.requestfunc.list.operation.perform',
method: 'resources.requestsugar.list.operation.perform',
logExtra: {
app_cli_id: 666,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/app-middlewares/after/checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const checkOutput = (output) => {
})
.map((check) => {
return check
.run(event.method, output.results, compiledApp)
.run(event.method, output.results, compiledApp, event.bundle)
.map((err) => ({ name: check.name, error: err }));
});
const checkResults = _.flatten(rawResults);
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/checks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ module.exports = {
triggerHasId: require('./trigger-has-id'),
firehoseSubscriptionIsArray: require('./firehose_is_array'),
firehoseSubscriptionKeyIsString: require('./firehose_is_string'),
performBulkReturnType: require('./perform-bulk-return-type'),
};
6 changes: 5 additions & 1 deletion packages/core/src/checks/is-create.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module.exports = (method) => {
// `method` will never start with "resources." in production.
// Seems only for testing.
return (
(method.startsWith('creates.') && method.endsWith('.operation.perform')) ||
(method.startsWith('creates.') &&
(method.endsWith('.operation.perform') ||
method.endsWith('.operation.performBulk'))) ||
(method.startsWith('resources.') &&
method.endsWith('.create.operation.perform'))
);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/checks/is-trigger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = (method) => {
return (
// `method` will never start with "resources.". Seems like legacy code.
// `method` will never start with "resources." in production.
// Seems only for testing.
(method.startsWith('triggers.') && method.endsWith('.operation.perform')) ||
(method.startsWith('resources.') &&
method.endsWith('.list.operation.perform'))
Expand Down
65 changes: 65 additions & 0 deletions packages/core/src/checks/perform-bulk-return-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const _ = require('lodash');

const performBulkEchoesIds = {
name: 'performBulkReturnType',

shouldRun: (method, bundle) => {
return (
Array.isArray(bundle.bulk) &&
method.endsWith('.operation.performBulk') &&
method.startsWith('creates.')
);
},

run: (method, results, compiledApp, bundle) => {
if (!_.isPlainObject(results)) {
// create-is-object should have caught this
return [];
}

const inputIds = bundle.bulk
.map((b) => {
return b && b.meta ? b.meta.id : null;
})
.filter((id) => id);

const outputIds = Object.keys(results);
const missingIds = inputIds.filter((id) => !outputIds.includes(id));

if (missingIds.length > 0) {
const LIMIT = 3;
let missingIdsStr = missingIds.slice(0, LIMIT).join(', ');
const remainingCount = missingIds.length - LIMIT;
if (remainingCount > 0) {
// Don't want to flood the user with too many IDs
missingIdsStr += `, and ${remainingCount} more`;
}
return [`Result object is missing these IDs as keys: ${missingIdsStr}`];
}

const errors = [];
for (const id of inputIds) {
const item = results[id];

if (!_.isPlainObject(item)) {
errors.push(`Result object member with ID '${id}' must be an object`);
} else if (
!_.isPlainObject(item.outputData) &&
typeof item.error !== 'string'
) {
errors.push(
`Result object member with ID '${id}' must have 'outputData' object or 'error' string`
);
}

if (errors.length >= 4) {
// No need to flood the user with too many errors
break;
}
}

return errors;
},
};

module.exports = performBulkEchoesIds;
72 changes: 72 additions & 0 deletions packages/core/test/checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,78 @@ describe('checks', () => {
isFirehoseWebhook(firehoseMethod).should.be.true();
isFirehoseWebhook('triggers.blah.operation.perform').should.be.false(); // the firehose webhook check is at app level, not trigger
});

it('should check performBulk missing IDs', () => {
const results = { one: {}, two: {} };
const bundle = {
bulk: [
{ meta: { id: 'one' } },
{ meta: { id: 'two' } },
{ meta: { id: 'three' } },
{ meta: { id: 'four' } },
{ meta: { id: 'five' } },
{ meta: { id: 'six' } },
{ meta: { id: 'seven' } },
{ meta: { id: 'eight' } },
{ meta: { id: 'nine' } },
],
};
const errors = checks.performBulkReturnType.run(
'creates.blah.operation.performBulk',
results,
{},
bundle
);
errors.length.should.eql(1);
errors[0].should.match(
/missing these IDs as keys: three, four, five, and 4 more/
);
});

it('should check performBulk object shape', () => {
const results = {
one: 'not an object',
two: { error: 123 },
three: { outputData: {} },
four: { error: 'test' },
five: {
outputData: 'this one should pass because it is not in the input',
},
};
const bundle = {
bulk: [
{ meta: { id: 'one' } },
{ meta: { id: 'two' } },
{ meta: { id: 'three' } },
{ meta: { id: 'four' } },
],
};
const errors = checks.performBulkReturnType.run(
'creates.blah.operation.performBulk',
results,
{},
bundle
);
errors.length.should.eql(2);
errors[0].should.match(/member with ID 'one' must be an object/);
errors[1].should.match(
/member with ID 'two' must have 'outputData' object or 'error' string/
);
});

it('should pass performBulk check', () => {
const results = { one: { outputData: {} }, two: { outputData: {} } };
const bundle = {
bulk: [{ meta: { id: 'one' } }, { meta: { id: 'two' } }],
};
const errors = checks.performBulkReturnType.run(
'creates.blah.operation.performBulk',
results,
{},
bundle
);
errors.should.be.empty();
});
});

describe('checkOutput', () => {
Expand Down
114 changes: 80 additions & 34 deletions packages/core/test/create-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ describe('create-app', () => {

const app = createApp(appDefinition);

const createTestInput = (method) => {
const createTestInput = (method, bundle) => {
const event = {
bundle: {},
command: 'execute',
bundle: { ...bundle },
method,
callback_url: 'calback_url',
};
Expand Down Expand Up @@ -108,13 +109,14 @@ describe('create-app', () => {
const input = createTestInput('resources.contact.list.operation.perform');
const output = await app(input);

should.exist(output.results.headers);
const result = output.results[0];
should.exist(result.headers);

// verify that custom http before middleware was applied
output.results.headers['X-Hashy'].should.deepEqual([
result.headers['X-Hashy'].should.deepEqual([
'1a3ba5251cb33ee7ade01af6a7b960b8',
]);
output.results.headers['X-Author'].should.deepEqual(['One Cool Dev']);
result.headers['X-Author'].should.deepEqual(['One Cool Dev']);
});

it('should fail on a live request call', async () => {
Expand Down Expand Up @@ -155,28 +157,18 @@ describe('create-app', () => {
throw new Error('expected an error');
});

it('should make call via z.request', async () => {
const input = createTestInput('triggers.requestfuncList.operation.perform');

const output = await app(input);

output.results[0].headers['X-Hashy'].should.deepEqual([
'1a3ba5251cb33ee7ade01af6a7b960b8',
]);
output.results[0].headers['X-Author'].should.deepEqual(['One Cool Dev']);
});

it('should make call via z.request with sugar url param', async () => {
const input = createTestInput(
'triggers.requestsugarList.operation.perform'
);

const output = await app(input);
const result = output.results[0];

output.results.headers['X-Hashy'].should.deepEqual([
result.headers['X-Hashy'].should.deepEqual([
'1a3ba5251cb33ee7ade01af6a7b960b8',
]);
output.results.headers['X-Author'].should.deepEqual(['One Cool Dev']);
result.headers['X-Author'].should.deepEqual(['One Cool Dev']);
});

it('should fail on a sync function', (done) => {
Expand Down Expand Up @@ -256,11 +248,13 @@ describe('create-app', () => {
const input = createTestInput('triggers.contactList.operation.perform');
const output = await app(input);

output.results.url.should.eql(`${HTTPBIN_URL}/get`);
output.results.headers['X-Hashy'].should.deepEqual([
const result = output.results[0];

result.url.should.eql(`${HTTPBIN_URL}/get`);
result.headers['X-Hashy'].should.deepEqual([
'1a3ba5251cb33ee7ade01af6a7b960b8',
]);
output.results.headers['X-Author'].should.deepEqual(['One Cool Dev']);
result.headers['X-Author'].should.deepEqual(['One Cool Dev']);
});

it('should return a rendered URL for OAuth2 authorizeURL', (done) => {
Expand Down Expand Up @@ -319,6 +313,46 @@ describe('create-app', () => {
.catch(done);
});

describe('output checks', () => {
it('should check performBulk output', async () => {
const definition = dataTools.jsonCopy(appDefinition);
definition.resources.row = {
key: 'row',
noun: 'Row',
create: {
display: {
label: 'Insert Row',
},
operation: {
performBulk: (z, bundle) => {
const firstId = bundle.bulk[0].meta.id;
return { [firstId]: {} };
},
},
},
};
const app = createApp(definition);
const bundle = {
bulk: [
{ meta: { id: 'ffee-0000' } },
{ meta: { id: 'ffee-0001' } },
{ meta: { id: 'ffee-0002' } },
{ meta: { id: 'ffee-0003' } },
{ meta: { id: 'ffee-0004' } },
{ meta: { id: 'ffee-0005' } },
],
};
const input = createTestInput(
'creates.rowCreate.operation.performBulk',
bundle
);
const err = await app(input).should.be.rejected();
err.name.should.eql('CheckError');
err.message.should.match(/missing these IDs as keys/);
err.message.should.match(/ffee-0001, ffee-0002, ffee-0003, and 2 more/);
});
});

describe('HTTP after middleware for auth refresh', () => {
// general purpose response tester
const testResponse = async (appDef, useShorthand, errorVerifier) => {
Expand Down Expand Up @@ -499,29 +533,37 @@ describe('create-app', () => {
});

describe('hydration', () => {
it('should hydrate method', (done) => {
it('should hydrate method', async () => {
const input = createTestInput(
'resources.honkerdonker.list.operation.perform'
);

app(input)
.then((output) => {
output.results.should.eql([
const output = await app(input);
output.results.should.eql([
{
id: 1,
$HOIST$:
'hydrate|||{"type":"method","method":"resources.honkerdonker.get.operation.perform","bundle":{"honkerId":1}}|||hydrate',
},
{
id: 2,
$HOIST$:
'hydrate|||{"type":"method","method":"resources.honkerdonker.get.operation.perform","bundle":{"honkerId":2}}|||hydrate',
},
{
id: 3,
$HOIST$:
'hydrate|||{"type":"method","method":"resources.honkerdonker.get.operation.perform","bundle":{"honkerId":3}}|||hydrate',
]);
done();
})
.catch(done);
},
]);
});
});

describe('calling a callback method', () => {
let results;
before(() =>
app(
createTestInput(
'resources.executeCallbackRequest.list.operation.perform'
'resources.executeCallbackRequest.create.operation.perform'
)
).then((output) => {
results = output;
Expand All @@ -530,8 +572,12 @@ describe('create-app', () => {

it('returns a CALLBACK envelope', () =>
results.status.should.eql('CALLBACK'));

it('returns the methods values', () =>
results.results.should.eql({ callbackUrl: 'calback_url' }));
results.results.should.eql({
id: 'dontcare',
callbackUrl: 'calback_url',
}));
});

describe('using require', () => {
Expand Down Expand Up @@ -579,13 +625,13 @@ describe('create-app', () => {
it('should import and use the crypto module from node', async () => {
const definition = createDefinition(`
const crypto = z.require('crypto');
return crypto.createHash('md5').update('abc').digest('hex');
return [{id: crypto.createHash('md5').update('abc').digest('hex')}];
`);
const input = createTestInput('triggers.testRequire.operation.perform');

const appPass = createApp(definition);
const { results } = await appPass(input);
results.should.eql('900150983cd24fb0d6963f7d28e17f72');
results[0].id.should.eql('900150983cd24fb0d6963f7d28e17f72');
});

it('should handle require errors', async () => {
Expand Down

0 comments on commit 6be0d3d

Please sign in to comment.