Skip to content

Commit bbdc5dc

Browse files
authored
Input binding returns null when blob does not exist (#67)
* Added test for blob trigger * Added more test cases for blob trigger
1 parent 7156bf2 commit bbdc5dc

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

azure/functions/blob.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ def encode(cls, obj: Any, *,
7878

7979
@classmethod
8080
def decode(cls, data: meta.Datum, *, trigger_metadata) -> Any:
81+
if data is None or data.type is None:
82+
return None
83+
8184
data_type = data.type
8285

8386
if data_type == 'string':

tests/test_blob.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import Dict, Any
5+
import unittest
6+
7+
import azure.functions as func
8+
import azure.functions.blob as afb
9+
from azure.functions.meta import Datum
10+
from azure.functions.blob import InputStream
11+
12+
13+
class TestBlob(unittest.TestCase):
14+
def test_blob_input_type(self):
15+
check_input_type = afb.BlobConverter.check_input_type_annotation
16+
self.assertTrue(check_input_type(str))
17+
self.assertTrue(check_input_type(bytes))
18+
self.assertTrue(check_input_type(InputStream))
19+
self.assertFalse(check_input_type(bytearray))
20+
21+
def test_blob_input_none(self):
22+
result: func.DocumentList = afb.BlobConverter.decode(
23+
data=None, trigger_metadata=None)
24+
self.assertIsNone(result)
25+
26+
def test_blob_input_string_no_metadata(self):
27+
datum: Datum = Datum(value='string_content', type='string')
28+
result: InputStream = afb.BlobConverter.decode(
29+
data=datum, trigger_metadata=None)
30+
self.assertIsNotNone(result)
31+
32+
# Verify result metadata
33+
self.assertIsInstance(result, InputStream)
34+
self.assertIsNone(result.name)
35+
self.assertIsNone(result.length)
36+
self.assertIsNone(result.uri)
37+
self.assertTrue(result.readable())
38+
self.assertFalse(result.seekable())
39+
self.assertFalse(result.writable())
40+
41+
# Verify result content
42+
content: bytes = result.read()
43+
self.assertEqual(content, b'string_content')
44+
45+
def test_blob_input_bytes_no_metadata(self):
46+
datum: Datum = Datum(value=b'bytes_content', type='bytes')
47+
result: InputStream = afb.BlobConverter.decode(
48+
data=datum, trigger_metadata=None)
49+
self.assertIsNotNone(result)
50+
51+
# Verify result metadata
52+
self.assertIsInstance(result, InputStream)
53+
self.assertIsNone(result.name)
54+
self.assertIsNone(result.length)
55+
self.assertIsNone(result.uri)
56+
self.assertTrue(result.readable())
57+
self.assertFalse(result.seekable())
58+
self.assertFalse(result.writable())
59+
60+
# Verify result content
61+
content: bytes = result.read()
62+
self.assertEqual(content, b'bytes_content')
63+
64+
def test_blob_input_with_metadata(self):
65+
datum: Datum = Datum(value=b'blob_content', type='bytes')
66+
metadata: Dict[str, Any] = {
67+
'Properties': Datum('{"Length": "12"}', 'json'),
68+
'BlobTrigger': Datum('blob_trigger_name', 'string'),
69+
'Uri': Datum('https://test.io/blob_trigger', 'string')
70+
}
71+
result: InputStream = afb.BlobConverter.decode(
72+
data=datum, trigger_metadata=metadata)
73+
74+
# Verify result metadata
75+
self.assertIsInstance(result, InputStream)
76+
self.assertEqual(result.name, 'blob_trigger_name')
77+
self.assertEqual(result.length, len(b'blob_content'))
78+
self.assertEqual(result.uri, 'https://test.io/blob_trigger')
79+
80+
def test_blob_incomplete_read(self):
81+
datum: Datum = Datum(value=b'blob_content', type='bytes')
82+
result: InputStream = afb.BlobConverter.decode(
83+
data=datum, trigger_metadata=None)
84+
85+
self.assertEqual(result.read(size=3), b'blo')
86+
87+
def test_blob_output_custom_output_content(self):
88+
class CustomOutput:
89+
def read(self) -> bytes:
90+
return b'custom_output_content'
91+
92+
# Try encoding a custom instance as an output return
93+
out = CustomOutput()
94+
result: Datum = afb.BlobConverter.encode(obj=out, expected_type=None)
95+
self.assertEqual(result.value, b'custom_output_content')
96+
self.assertEqual(result.type, 'bytes')
97+
98+
def test_blob_output_custom_output_without_read_method(self):
99+
class CustomOutput:
100+
def _read(self) -> bytes:
101+
return b'should_not_be_called'
102+
103+
# Try encoding a custom instance without read() method
104+
# This should raise an error when an unknown output is returned
105+
out = CustomOutput()
106+
with self.assertRaises(NotImplementedError):
107+
afb.BlobConverter.encode(obj=out, expected_type=None)
108+
109+
def test_blob_output_string(self):
110+
out: str = 'blob_output_string'
111+
result: Datum = afb.BlobConverter.encode(obj=out, expected_type=None)
112+
self.assertEqual(result.value, 'blob_output_string')
113+
self.assertEqual(result.type, 'string')
114+
115+
def test_blob_output_bytes(self):
116+
out: bytes = b'blob_output_bytes'
117+
result: Datum = afb.BlobConverter.encode(obj=out, expected_type=None)
118+
self.assertEqual(result.value, b'blob_output_bytes')
119+
self.assertEqual(result.type, 'bytes')
120+
121+
def test_blob_output_type(self):
122+
check_output_type = afb.BlobConverter.check_output_type_annotation
123+
self.assertTrue(check_output_type(str))
124+
self.assertTrue(check_output_type(bytes))
125+
self.assertTrue(check_output_type(bytearray))
126+
self.assertTrue(check_output_type(InputStream))
127+
128+
def test_blob_output_custom_type(self):
129+
class CustomOutput:
130+
def read(self) -> Datum:
131+
return Datum(b'custom_output_content', 'types')
132+
133+
check_output_type = afb.BlobConverter.check_output_type_annotation
134+
self.assertTrue(check_output_type(CustomOutput))

0 commit comments

Comments
 (0)