blob: 5d002c2f284072ec779626533f5a270e6ddb0dc7 [file] [log] [blame]
import composer
import pytest
import os
name = 'TestAction'
wsk = composer.openwhisk({ 'ignore_certs': 'IGNORE_CERTS' in os.environ and os.environ['IGNORE_CERTS'] == 'true' })
def define(action):
''' deploy action '''
try:
wsk.actions.delete(action['name'])
except:
pass
wsk.actions.create(action)
def invoke(task, params = {}, blocking = True):
''' deploy and invoke composition '''
wsk.compositions.deploy(composer.composition(name, task))
return wsk.actions.invoke({ 'name': name, 'params': params, 'blocking': blocking })
@pytest.fixture(scope="session", autouse=True)
def deploy_actions():
define({ 'name': 'echo', 'action': 'const main = x=>x', 'kind': 'nodejs:default' })
define({ 'name': 'DivideByTwo', 'action': 'function main({n}) { return { n: n / 2 } }', 'kind': 'nodejs:default'})
define({ 'name': 'TripleAndIncrement', 'action': 'function main({n}) { return { n: n * 3 + 1 } }', 'kind': 'nodejs:default' })
define({ 'name': 'isNotOne', 'action': 'function main({n}) { return { value: n != 1 } }', 'kind': 'nodejs:default' })
define({ 'name': 'isEven', 'action': 'function main({n}) { return { value: n % 2 == 0 } }', 'kind': 'nodejs:default'})
class TestAction:
def test_action_true(self):
''' action must return true '''
activation = invoke(composer.action('isNotOne'), { 'n': 0 })
assert activation['response']['result'] == { 'value': True }
def test_action_false(self):
''' action must return false '''
activation = invoke(composer.action('isNotOne'), { 'n': 1 })
assert activation['response']['result'] == { 'value': False }
def test_parse_action_name(self):
combos = [
{ "n": "", "s": False, "e": "Name is not specified" },
{ "n": " ", "s": False, "e": "Name is not specified" },
{ "n": "/", "s": False, "e": "Name is not valid" },
{ "n": "//", "s": False, "e": "Name is not valid" },
{ "n": "/a", "s": False, "e": "Name is not valid" },
{ "n": "/a/b/c/d", "s": False, "e": "Name is not valid" },
{ "n": "/a/b/c/d/", "s": False, "e": "Name is not valid" },
{ "n": "a/b/c/d", "s": False, "e": "Name is not valid" },
{ "n": "/a/ /b", "s": False, "e": "Name is not valid" },
{ "n": "a", "e": False, "s": "/_/a" },
{ "n": "a/b", "e": False, "s": "/_/a/b" },
{ "n": "a/b/c", "e": False, "s": "/a/b/c" },
{ "n": "/a/b", "e": False, "s": "/a/b" },
{ "n": "/a/b/c", "e": False, "s": "/a/b/c" }
]
for combo in combos:
if combo["s"] is not False:
# good cases
assert composer.parse_action_name(combo["n"]) == combo["s"]
else:
# error cases
try:
composer.parse_action_name(combo["n"])
assert False
except composer.ComposerError as error:
assert error.message == combo["e"]
def test_invalid(self):
'''invalid argument'''
try:
invoke(composer.action(42))
assert False
except composer.ComposerError as error:
assert error.message == 'Name is not valid'
@pytest.mark.literal
class TestLiteral:
def test_boolean(self):
activation = invoke(composer.literal(True))
assert activation['response']['result'] == { 'value': True }
def test_number(self):
activation = invoke(composer.literal(42))
assert activation['response']['result'] == { 'value': 42 }
def test_invalid_arg(self):
try:
composer.literal(lambda x:x)
assert False
except composer.ComposerError as error:
assert error.message == 'Invalid argument'
@pytest.mark.skip(reason='need python conductor')
class TestFunction:
def test_function_true(self):
activation = invoke(composer.function(lambda args: args['n'] % 2 == 0), { 'n': 4 })
assert activation['response']['result'] == { 'value': True }
class TestTasks:
def test_task_action(self):
activation = invoke(composer.task('isNotOne'), { 'n': 0 })
assert activation['response']['result'] == { 'value': True }
@pytest.mark.skip(reason='need python conductor')
def test_task_function(self):
activation = invoke(composer.task(lambda args: args['n'] % 2 == 0), { 'n': 4 })
assert activation['response']['result'] == { 'value': True }
def test_task_none(self):
activation = invoke(composer.task(None), { 'foo': 'foo' })
assert activation['response']['result'] == { 'foo': 'foo' }
def test_task_fail(self):
'''none task must fail on error input'''
try:
invoke(composer.task(None), { 'error': 'foo' })
assert False
except Exception as error:
print(error)
class TestSequence:
def test_flat(self):
activation = invoke(composer.sequence('TripleAndIncrement', 'DivideByTwo', 'DivideByTwo'), { 'n': 5 })
assert activation['response']['result'] == { 'n': 4 }
def test_nested_right(self):
activation = invoke(composer.sequence('TripleAndIncrement', composer.sequence('DivideByTwo', 'DivideByTwo')), { 'n': 5 })
assert activation['response']['result'] == { 'n': 4 }
def test_nested_left(self):
activation = invoke(composer.sequence(composer.sequence('TripleAndIncrement', 'DivideByTwo'), 'DivideByTwo'), { 'n': 5 })
assert activation['response']['result'] == { 'n': 4 }
def test_seq(self):
activation = invoke(composer.seq('TripleAndIncrement', 'DivideByTwo', 'DivideByTwo'), { 'n': 5 })
assert activation['response']['result'] == { 'n': 4 }
class TestIf:
def test_condition_true(self):
activation = invoke(composer.when('isEven', 'DivideByTwo', 'TripleAndIncrement'), { 'n': 4 })
assert activation['response']['result'] == { 'n': 2 }
def test_condition_false(self):
activation = invoke(composer.when('isEven', 'DivideByTwo', 'TripleAndIncrement'), { 'n': 3 })
assert activation['response']['result'] == { 'n': 10 }
def test_condition_true_then_branch_only(self):
activation = invoke(composer.when('isEven', 'DivideByTwo'), { 'n': 4 })
assert activation['response']['result'] == { 'n': 2 }
def test_condition_false_then_branch_only(self):
activation = invoke(composer.when('isEven', 'DivideByTwo'), { 'n': 3 })
assert activation['response']['result'] == { 'n': 3 }
# def test_condition_true_nosave_option(self):
# activation = invoke(composer.if_nosave('isEven', params => { params.then = true }, params => { params.else = true }), { n: 2 })
# assert activation['response']['result'] == { value: true, then: true }))
# def test_condition_false_nosave_option(self):
# activation = invoke(composer.if_nosave('isEven', params => { params.then = true }, params => { params.else = true }), { n: 3 })
# assert activation['response']['result'] == { value: false, else: true }))
class TestLoop:
@pytest.mark.skip(reason='need python conductor')
def test_a_few_iterations(self) :
activation = invoke(composer.loop('isNotOne', '({ n }) => ({ "n": n - 1 }))', { 'n': 4 }))
assert activation['response']['result'] == { 'n': 1 }
@pytest.mark.skip(reason='need python conductor')
def test_no_iteration(self):
activation = invoke(composer.loop('() => false', '({ n }) => ({ "n": n - 1 }))', { 'n': 1 }))
assert activation['response']['result'] == { 'n': 1 }
@pytest.mark.skip(reason='need python conductor')
def test_nosave_option(self) :
activation = invoke(composer.loop_nosave('({ n }) => ({ n, value: n !== 1 })', '({ n }) => ({ n: n - 1 }))', { 'n': 4 }))
assert activation['response']['result'] == { 'value': False, 'n': 1 }
@pytest.mark.skip(reason='need python conductor')
class TestDoLoop:
def test_a_few_iterations(self) :
activation = invoke(composer.doloop('({ n }) => ({ n: n - 1 })', 'isNotOne'), { 'n': 4 })
assert activation['response']['result'] == { 'n': 1 }
def test_one_iteration(self) :
activation = invoke(composer.doloop('({ n }) => ({ n: n - 1 })', '() => false'), { 'n': 1 })
assert activation['response']['result'] == { 'n': 0 }
def test_nosave_option(self) :
activation = invoke(composer.doloop_nosave(('{ n }) => ({ n: n - 1 })', '({ n }) => ({ n, value: n !== 1 }))', { 'n': 4 })))
assert activation['response']['result'] == { 'value': False, 'n': 1 }
@pytest.mark.skip(reason='need python conductor')
class TestDo: # Try
def test_no_error(self):
activation = invoke(composer.do('() => true', 'error => ({ message: error.error })'))
assert activation['response']['result'] == { 'value': True }
def test_error(self) :
activation = invoke(composer.do('() => ({ error: "foo" })', 'error => ({ message: error.error })'))
assert activation['response']['result'] == { 'message': 'foo' }
def test_try_must_throw(self) :
activation = invoke(composer.do(composer.task(None), 'error => ({ message: error.error }))', { 'error': 'foo' }))
assert activation['response']['result'] == { 'message': 'foo' }
def test_while_must_throw(self) :
activation = invoke(composer.do(composer.loop(composer.literal(False), None), 'error => ({ message: error.error })'), { 'error': 'foo' })
assert activation['response']['result'] == { 'message': 'foo' }
def test_if_must_throw(self) :
activation = invoke(composer.do(composer.when(composer.literal(False), None), 'error => ({ message: error.error })'), { 'error': 'foo' })
assert activation['response']['result'] == { 'message': 'foo' }
def test_retain(self) :
activation = invoke(composer.retain(composer.do('() => ({ p: 4 })', None)), { 'n': 3 })
assert activation['response']['result'] == { 'params': { 'n': 3 }, 'result': { 'p': 4 } }
@pytest.mark.skip(reason='need python conductor')
class TestEnsure: # Finally
def test_no_error(self) :
activation = invoke(composer.ensure('() => true', 'params => ({ params })'))
assert activation['response']['result'] == { 'params': { 'value': True } }
def test_error(self) :
activation = invoke(composer.ensure('() => ({ error: "foo" })', 'params => ({ params })'))
assert activation['response']['result'] == { 'params': { 'error': 'foo' } }
@pytest.mark.skip(reason='need python conductor')
class TestLet:
def test_one_variable(self) :
activation = invoke(composer.let({ 'x': 42 }, '() => x'))
assert activation['response']['result'] == { 'value': 42 }
def test_masking(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 }, '() => x')))
assert activation['response']['result'] == { value: 69 }
def test_two_variables(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'y': 69 }, '() => x + y')))
assert activation['response']['result'] == { 'value': 111 }
def test_two_variables_combined(self) :
activation = invoke(composer.let({ 'x': 42, 'y': 69 }, '() => x + y'))
assert activation['response']['result'] == { 'value': 111 }
def test_scoping(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 }, '() => x'), '({ value }) => value + x'))
assert activation['response']['result'] == { 'value': 111 }
def test_invalid_argument(self):
try:
invoke(composer.let(invoke))
assert False
except Exception as error:
assert error.message.startswith('Invalid argument')
@pytest.mark.skip(reason='need python conductor')
class TestMask:
def test_let_let_mask(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 }, composer.mask('() => x'))))
assert activation['response']['result'] == { 'value': 42 }
def test_let_mask_let(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.mask(composer.let({ 'x': 69 }, '() => x'))))
assert activation['response']['result'] == { 'value': 69 }
def test_let_let_try_mask(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 },
composer.do(composer.mask('() => x'), '() => { }'))))
assert activation['response']['result'] == { 'value': 42 }
def test_let_let_let_mask(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 },
composer.let({ 'x': -1 }, composer.mask('() => x')))))
assert activation['response']['result'] == { 'value': 69 }
def test_let_let_let_mask_mask(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 },
composer.let({ 'x': -1 }, composer.mask(composer.mask('() => x'))))))
assert activation['response']['result'] == { 'value': 42 }
def test_let_let_mask_let_mask(self) :
activation = invoke(composer.let({ 'x': 42 }, composer.let({ 'x': 69 },
composer.mask(composer.let({ 'x': -1 }, composer.mask('() => x'))))))
assert activation['response']['result'] == { 'value': 42 }
class TestRetain:
def test_base_case(self) :
activation = invoke(composer.retain('TripleAndIncrement'), { 'n': 3 })
assert activation['response']['result'] == { 'params': { 'n': 3 }, 'result': { 'n': 10 } }
@pytest.mark.skip(reason='need python conductor')
def test_throw_error(self) :
try:
activation = invoke(composer.retain('() => ({ error: "foo" })'), { 'n': 3 })
assert False
except composer.ComposerError as error:
assert error.error.response.result == { 'error': 'foo' }
@pytest.mark.skip(reason='need python conductor')
def test_catch_error(self) :
try:
activation = invoke(composer.retain_catch('() => ({ error: "foo" })'), { 'n': 3 })
except Exception as activation:
assert activation['response']['result'] == { 'params': { 'n': 3 }, 'result': { 'error': 'foo' } }
class TestRepeat:
def test_a_few_iterations(self) :
activation = invoke(composer.repeat(3, 'DivideByTwo'), { 'n': 8 })
assert activation['response']['result'] == { 'n': 1 }
def test_invalid_argument(self) :
try:
invoke(composer.repeat('foo'))
assert False
except composer.ComposerError as error:
assert error.message.startswith('Invalid argument')
class TestRetry:
@pytest.mark.skip(reason='need python conductor')
def test_success(self) :
activation = invoke(composer.let({ 'x': 2 }, composer.retry(2, '() => x-- > 0 ? { error: "foo" } : 42')))
assert activation['response']['result'] == { 'value': 42 }
@pytest.mark.skip(reason='need python conductor')
def test_failure(self) :
try:
activation = invoke(composer.let({ 'x': 2 }, composer.retry(1, '() => x-- > 0 ? { error: "foo" } : 42')))
assert False
except Exception as activation:
assert activation.error.response.result.error, 'foo'
def test_invalid_argument(self) :
try:
invoke(composer.retry('foo'))
assert False
except Exception as error:
assert error.message.startswith('Invalid argument')