Skip to content

Commit 17a69f5

Browse files
authored
Add unittests for generic bindings and durable functions (#624)
* Add unittests for generic bindings * Add a case for testing no annotation scenarios * Fix generic binding without datatype test
1 parent 0fccf5a commit 17a69f5

File tree

16 files changed

+304
-0
lines changed

16 files changed

+304
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "activityTrigger",
6+
"name": "input",
7+
"direction": "in"
8+
}
9+
]
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def main(input: str) -> str:
2+
return input
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "activityTrigger",
6+
"name": "input",
7+
"direction": "in"
8+
}
9+
]
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def main(input):
2+
return input
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "orchestrationTrigger",
6+
"name": "context",
7+
"direction": "in"
8+
}
9+
]
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# import azure.durable_functions as df
2+
3+
4+
def generator_function(context):
5+
final_result = yield context.call_activity('activity_trigger', 'foobar')
6+
return final_result
7+
8+
9+
def main(context):
10+
# orchestrate = df.Orchestrator.create(generator_function)
11+
# result = orchestrate(context)
12+
# return result
13+
return f'{context} :)'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "foobar",
6+
"name": "input",
7+
"direction": "in",
8+
"dataType": "binary"
9+
},
10+
{
11+
"direction": "out",
12+
"name": "$return",
13+
"type": "foobar",
14+
"dataType": "binary"
15+
}
16+
]
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Input as bytes, without annotation
2+
def main(input):
3+
return input
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "foobar",
6+
"name": "input",
7+
"direction": "in",
8+
"dataType": "string"
9+
},
10+
{
11+
"direction": "out",
12+
"name": "$return",
13+
"type": "foobar",
14+
"dataType": "string"
15+
}
16+
]
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Input as string, without annotation
2+
def main(input):
3+
return input
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "foobar",
6+
"name": "input",
7+
"direction": "in",
8+
"dataType": "string"
9+
}
10+
]
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Input as string, without annotation
2+
def main(input: str):
3+
return input
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"scriptFile": "main.py",
3+
"bindings": [
4+
{
5+
"type": "foobar",
6+
"name": "input",
7+
"direction": "in"
8+
}
9+
]
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def main(input: str) -> str:
2+
return input
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from azure_functions_worker import protos
2+
from azure_functions_worker import testutils
3+
4+
5+
class TestDurableFunctions(testutils.AsyncTestCase):
6+
durable_functions_dir = testutils.UNIT_TESTS_FOLDER / 'durable_functions'
7+
8+
async def test_mock_activity_trigger(self):
9+
async with testutils.start_mockhost(
10+
script_root=self.durable_functions_dir) as host:
11+
12+
func_id, r = await host.load_function('activity_trigger')
13+
14+
self.assertEqual(r.response.function_id, func_id)
15+
self.assertEqual(r.response.result.status,
16+
protos.StatusResult.Success)
17+
18+
_, r = await host.invoke_function(
19+
'activity_trigger', [
20+
protos.ParameterBinding(
21+
name='input',
22+
data=protos.TypedData(
23+
string='test'
24+
)
25+
)
26+
]
27+
)
28+
self.assertEqual(r.response.result.status,
29+
protos.StatusResult.Success)
30+
self.assertEqual(
31+
r.response.return_value,
32+
protos.TypedData(string='test')
33+
)
34+
35+
async def test_mock_activity_trigger_no_anno(self):
36+
async with testutils.start_mockhost(
37+
script_root=self.durable_functions_dir) as host:
38+
39+
func_id, r = await host.load_function('activity_trigger_no_anno')
40+
41+
self.assertEqual(r.response.function_id, func_id)
42+
self.assertEqual(r.response.result.status,
43+
protos.StatusResult.Success)
44+
45+
_, r = await host.invoke_function(
46+
'activity_trigger_no_anno', [
47+
protos.ParameterBinding(
48+
name='input',
49+
data=protos.TypedData(
50+
bytes=b'\x34\x93\x04\x70'
51+
)
52+
)
53+
]
54+
)
55+
self.assertEqual(r.response.result.status,
56+
protos.StatusResult.Success)
57+
self.assertEqual(
58+
r.response.return_value,
59+
protos.TypedData(bytes=b'\x34\x93\x04\x70')
60+
)
61+
62+
async def test_mock_orchestration_trigger(self):
63+
async with testutils.start_mockhost(
64+
script_root=self.durable_functions_dir) as host:
65+
66+
func_id, r = await host.load_function('orchestration_trigger')
67+
68+
self.assertEqual(r.response.function_id, func_id)
69+
self.assertEqual(r.response.result.status,
70+
protos.StatusResult.Success)
71+
72+
_, r = await host.invoke_function(
73+
'orchestration_trigger', [
74+
protos.ParameterBinding(
75+
name='context',
76+
data=protos.TypedData(
77+
string='Durable functions coming soon'
78+
)
79+
)
80+
]
81+
)
82+
self.assertEqual(r.response.result.status,
83+
protos.StatusResult.Success)
84+
self.assertEqual(
85+
r.response.return_value,
86+
protos.TypedData(string='Durable functions coming soon :)')
87+
)

tests/unittests/test_mock_generic_functions.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,107 @@ async def test_mock_generic_as_bytes(self):
5858
r.response.return_value,
5959
protos.TypedData(bytes=b'\x00\x01')
6060
)
61+
62+
async def test_mock_generic_as_str_no_anno(self):
63+
async with testutils.start_mockhost(
64+
script_root=self.generic_funcs_dir) as host:
65+
66+
func_id, r = await host.load_function('foobar_as_str_no_anno')
67+
68+
self.assertEqual(r.response.function_id, func_id)
69+
self.assertEqual(r.response.result.status,
70+
protos.StatusResult.Success)
71+
72+
_, r = await host.invoke_function(
73+
'foobar_as_str_no_anno', [
74+
protos.ParameterBinding(
75+
name='input',
76+
data=protos.TypedData(
77+
string='test'
78+
)
79+
)
80+
]
81+
)
82+
self.assertEqual(r.response.result.status,
83+
protos.StatusResult.Success)
84+
self.assertEqual(
85+
r.response.return_value,
86+
protos.TypedData(string='test')
87+
)
88+
89+
async def test_mock_generic_as_bytes_no_anno(self):
90+
async with testutils.start_mockhost(
91+
script_root=self.generic_funcs_dir) as host:
92+
93+
func_id, r = await host.load_function('foobar_as_bytes_no_anno')
94+
95+
self.assertEqual(r.response.function_id, func_id)
96+
self.assertEqual(r.response.result.status,
97+
protos.StatusResult.Success)
98+
99+
_, r = await host.invoke_function(
100+
'foobar_as_bytes_no_anno', [
101+
protos.ParameterBinding(
102+
name='input',
103+
data=protos.TypedData(
104+
bytes=b'\x00\x01'
105+
)
106+
)
107+
]
108+
)
109+
self.assertEqual(r.response.result.status,
110+
protos.StatusResult.Success)
111+
self.assertEqual(
112+
r.response.return_value,
113+
protos.TypedData(bytes=b'\x00\x01')
114+
)
115+
116+
async def test_mock_generic_should_not_support_implicit_output(self):
117+
async with testutils.start_mockhost(
118+
script_root=self.generic_funcs_dir) as host:
119+
120+
func_id, r = await host.load_function('foobar_implicit_output')
121+
122+
self.assertEqual(r.response.function_id, func_id)
123+
self.assertEqual(r.response.result.status,
124+
protos.StatusResult.Success)
125+
126+
_, r = await host.invoke_function(
127+
'foobar_as_bytes_no_anno', [
128+
protos.ParameterBinding(
129+
name='input',
130+
data=protos.TypedData(
131+
bytes=b'\x00\x01'
132+
)
133+
)
134+
]
135+
)
136+
# It should fail here, since generic binding requires
137+
# $return statement in function.json to pass output
138+
self.assertEqual(r.response.result.status,
139+
protos.StatusResult.Failure)
140+
141+
async def test_mock_generic_should_support_without_datatype(self):
142+
async with testutils.start_mockhost(
143+
script_root=self.generic_funcs_dir) as host:
144+
145+
func_id, r = await host.load_function('foobar_with_no_datatype')
146+
147+
self.assertEqual(r.response.function_id, func_id)
148+
self.assertEqual(r.response.result.status,
149+
protos.StatusResult.Success)
150+
151+
_, r = await host.invoke_function(
152+
'foobar_with_no_datatype', [
153+
protos.ParameterBinding(
154+
name='input',
155+
data=protos.TypedData(
156+
bytes=b'\x00\x01'
157+
)
158+
)
159+
]
160+
)
161+
# It should fail here, since the generic binding requires datatype
162+
# to be defined in function.json
163+
self.assertEqual(r.response.result.status,
164+
protos.StatusResult.Failure)

0 commit comments

Comments
 (0)