@wmfs/statebox
Version:
Orchestrate Node functions using Amazon States Language
265 lines (235 loc) • 8.93 kB
JavaScript
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const _ = require('lodash')
const intrinsicFunctions = require('../lib/state-machines/state-types/instrinsics')
const intrinsicStateMachines = require('./fixtures/state-machines/intrinsic-functions')
const inputPathTokeniser = require('../lib/state-machines/state-types/path-handlers/input-path-tokeniser')
const Statebox = require('./../lib')
describe('Intrinsic Functions', function () {
this.timeout(process.env.TIMEOUT || 5000)
describe('Function parsing', () => {
describe('function call', () => {
const goodCalls = [
"States.Format('hello {}', 'world')",
'States.StringToJson($path)',
'States.JsonToString($path)',
'States.Array()',
'States.Array(99)',
'States.Array(true)',
'States.Array(false)',
"States.Array('fridge-freezer')",
'States.Array(null)',
'States.Array(States.Array(1, 2))',
'States.Array(1, 2, States.Array(3, 4))',
'States.Array(States.Array(1, 2), 3, 4)',
'States.Array(1, 2, States.Array(3, 4), 5, 6)'
]
for (const call of goodCalls) {
it(call, () => {
inputPathTokeniser(call)
})
}
})
describe('bad function calls', () => {
const notCalls = [
'Madeup.Function()',
'true',
'99',
'States.Array(undefined)'
]
for (const call of notCalls) {
it(call, () => {
expect(() => inputPathTokeniser(call)).to.throw()
})
}
})
describe('tokenise arguments', () => {
const args = [
["'a string'", 'string:a string'],
['123', 'number:123'],
['123.45', 'number:123.45'],
['-123', 'number:-123'],
['-123.45', 'number:-123.45'],
['true', 'boolean:true'],
['false', 'boolean:false'],
['null', 'null:null'],
['$.path', 'path:$.path'],
['$.array[0:2]', 'path:$.array[0:2]'],
["'\\''", "string:\\'"],
["'\\}'", 'string:\\}'],
["'\\{'", 'string:\\{'],
["'\\\\'", 'string:\\\\'],
["'embedded \\{\\'hello\\'\\}'", "string:embedded \\{\\'hello\\'\\}"]
]
const context = {
path: 'path value',
array: ['one', 'two', 'three']
}
function parseArguments (args) {
return inputPathTokeniser(`States.Array(${args})`).parameters.map(tok => `${tok.type}:${tok.value}`)
}
for (const [arg, result] of args) {
it(arg, () => {
expect(parseArguments(arg, context)).to.eql([result])
})
for (const [arg2, result2] of args) {
const ts = [arg, arg2].join()
it(ts, () => {
expect(parseArguments(ts, context)).to.eql([result, result2])
})
}
}
const withWhitespace = [
['123, \'happy meal\'', ['number:123', 'string:happy meal']],
['123 ,\'happy meal\'', ['number:123', 'string:happy meal']],
['123 , \'happy meal\'', ['number:123', 'string:happy meal']],
['123,\'happy meal\' ', ['number:123', 'string:happy meal']],
[' 123,\'happy meal\'', ['number:123', 'string:happy meal']],
[' 123 , \'happy meal\' ', ['number:123', 'string:happy meal']],
[' 123 , \' happy meal \' ', ['number:123', 'string: happy meal ']]
]
for (const [token, value] of withWhitespace) {
it(token, () => {
expect(parseArguments(token)).to.eql(value)
})
}
})
})
describe('States.Format', () => {
const goodFormatTests = [
[['test'], 'test'],
[['insert ->{}<- here', 'word'], 'insert ->word<- here'],
[['insert ->{}<- here', true], 'insert ->true<- here'],
[['insert ->{}<- here', 1], 'insert ->1<- here'],
[['insert ->{}<- here', 1452.1212], 'insert ->1452.1212<- here'],
[['insert ->{}<- here', null], 'insert ->null<- here'],
[['{}, {}, {}', 'word', 100, true], 'word, 100, true'],
[['{}<-at start', 'here'], 'here<-at start'],
[['at end->{}', 'here'], 'at end->here'],
[['{}', null], 'null'],
[['{}{}', null, null], 'nullnull'],
[['{}', '\\{\\}'], '{}'],
[["\\\\\\{\\'Socks\\'\\}\\\\"], "\\{'Socks'}\\"],
[['{}', "\\\\\\{\\'Socks\\'\\}\\\\"], "\\{'Socks'}\\"],
[['\\\\\\{{}\\}\\\\', "\\'Socks\\'"], "\\{'Socks'}\\"]
]
for (const [args, expected] of goodFormatTests) {
it(`States.Format(${args.map(a => '"' + a + '"').join(', ')})`, () => {
const result = intrinsicFunctions.Format(...args)
expect(result).to.equal(expected)
})
}
})
describe('States.Array', () => {
const arrays = [
[],
[1, 2, 3],
['a', { an: 'object' }, false]
]
for (const array of arrays) {
it(`States.Array(${array.map(a => JSON.stringify(a)).join()})`, () => {
const result = intrinsicFunctions.Array(...array)
expect(result).to.eql(array)
})
}
})
describe('States.StringToJson', () => {
const strings = [
['"hello"', 'hello'],
['99', 99],
['{"fruit": "basket"}', { fruit: 'basket' }],
['[1, 2, 3]', [1, 2, 3]]
]
for (const [string, expected] of strings) {
it(`States.StringToJson('${string}')`, () => {
const json = intrinsicFunctions.StringToJson(string)
expect(json).to.eql(expected)
})
}
})
describe('States.JsonToString', () => {
const strings = [
['hello', '"hello"'],
[99, '99'],
[{ fruit: 'basket' }, '{"fruit":"basket"}'],
[[1, 2, 3], '[1,2,3]']
]
for (const [obj, expected] of strings) {
it(`States.JsonToString('${obj}')`, () => {
const string = intrinsicFunctions.JsonToString(obj)
expect(string).to.eql(expected)
})
}
})
describe('In State Machines', () => {
let statebox
before('setup statebox', async () => {
statebox = new Statebox()
await statebox.ready
await statebox.createStateMachines(intrinsicStateMachines, {})
})
const functionTests = {
StringToJson: [
['object', { someString: '{"hello":"world"}' }, { hello: 'world' }],
['string', { someString: '"hello"' }, 'hello'],
['number', { someString: '2020' }, 2020],
['boolean', { someString: 'true' }, true],
['noParams', null, null, false],
['tooManyParams', null, null, false]
],
JsonToString: [
['object', { someJson: { name: 'Foo', year: 2020 }, zebra: 'stripe' }, '{"name":"Foo","year":2020}'],
['number', { someJson: { name: 'Foo', year: 2020 }, zebra: 'stripe' }, '2020'],
['string', { someJson: { name: 'Foo', year: 2020 }, zebra: 'stripe' }, '"stripe"'],
['paramIsNotAPath', null, null, false],
['noParams', null, null, false],
['tooManyParams', null, null, false]
],
Format: [
['format', { name: 'Homer' }, 'Your name is Homer, we are in the year 2020'],
['everything', null, 'string 100 true null'],
['tooManyParams', null, null, false],
['notEnoughParams', null, null, false],
['badParameter', { name: 'Homer' }, null, false],
['noFormatString', null, null, false],
['badFormatString', null, null, false]
],
Array: [
['array', {
someJson: { random: 'abcdefg' },
zebra: 'stripe'
}, ['Foo', 2020, { random: 'abcdefg' }, null]],
['emptyArray', null, []],
['numbers', null, [1, 2, 3, 4]],
['strings', null, ['one', 'two']],
['nestedStatesArray', null, ['one', 'two', ['three', 'four', 'five'], 'six']]
]
}
for (const [func, tests] of Object.entries(functionTests)) {
describe(func, () => {
for (const [testName, input, result, good = true] of tests) {
const stateMachineName = `${func}_${testName}`
it(_.startCase(testName), async () => {
let executionDescription = await statebox.startExecution(
Object.assign({}, input),
stateMachineName,
{} // options
)
executionDescription = await statebox.waitUntilStoppedRunning(executionDescription.executionName)
if (good) {
expect(executionDescription.status).to.eql('SUCCEEDED')
expect(executionDescription.stateMachineName).to.eql(stateMachineName)
expect(executionDescription.currentResource).to.eql(undefined)
expect(executionDescription.ctx.foo).to.eql(result)
} else {
expect(executionDescription.status).to.eql('FAILED')
expect(executionDescription.errorCode).to.eql('States.IntrinsicFailure')
}
}) // it ...
}
})
}
}) // called from state machines
})