Skip to content

Commit

Permalink
Improve the code to pass flake8 and mypy check
Browse files Browse the repository at this point in the history
  • Loading branch information
yihong1120 committed Dec 8, 2024
1 parent 5033c4b commit 0fcf27f
Showing 1 changed file with 97 additions and 67 deletions.
164 changes: 97 additions & 67 deletions tests/src/notifiers/line_notifier_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,113 +21,120 @@ class TestLineNotifier(unittest.IsolatedAsyncioTestCase):

def setUp(self) -> None:
"""
Set up method to initialise test variables and LineNotifier instance.
Sets up the test environment by initialising test variables
and a LineNotifier instance.
"""
# Mock LINE Notify token for testing
self.line_token: str = 'test_token'

# Mock message to send
self.message: str = 'Test Message'
self.image: np.ndarray = np.zeros((100, 100, 3), dtype=np.uint8)
self.image: np.ndarray = np.zeros(
(100, 100, 3), dtype=np.uint8,
) # Mock image
self.notifier: LineNotifier = LineNotifier()

@patch.dict('os.environ', {'LINE_NOTIFY_TOKEN': 'test_env_token'})
@patch('aiohttp.ClientSession.post')
async def test_init_with_env_token(self, mock_post: MagicMock) -> None:
"""
Test case for sending a notification using an environment token.
Tests the notification sending functionality
using an environment variable token.
Args:
mock_post (MagicMock): Mock object for aiohttp.ClientSession.post.
"""
# Mock the response from aiohttp.ClientSession.post
mock_response: MagicMock = MagicMock()
mock_response.status = 200 # Simulate a successful request
mock_response.status = 200 # Simulate a successful response
mock_post.return_value.__aenter__.return_value = mock_response

notifier: LineNotifier = LineNotifier()
status_code: int = await notifier.send_notification(self.message)

# Assert that the status code is 200
# Assert that the HTTP response status is 200
self.assertEqual(status_code, 200)
mock_post.assert_called_once_with(
'https://notify-api.line.me/api/notify',
headers={'Authorization': 'Bearer test_env_token'},
params={'message': self.message},
data=None,
)
# Ensure that aiohttp's post method was called exactly once
mock_post.assert_called_once()

async def test_init_without_token(self) -> None:
"""
Test case for sending a notification without a token
(expects ValueError).
Tests the behaviour when attempting to send a notification
without providing a token.
Raises:
ValueError: Expected exception when no token is provided.
"""
with self.assertRaises(ValueError):
await self.notifier.send_notification(
self.message,
line_token=None,
self.message, line_token=None,
)

@patch('aiohttp.ClientSession.post')
async def test_send_notification_without_image(
self,
mock_post: MagicMock,
self, mock_post: MagicMock,
) -> None:
"""
Test case for sending notification without an image.
Tests sending a notification without an image.
Args:
mock_post (MagicMock): Mock object for aiohttp.ClientSession.post.
"""
mock_response: MagicMock = MagicMock()
mock_response.status = 200
mock_response.status = 200 # Simulate a successful response
mock_post.return_value.__aenter__.return_value = mock_response

status_code: int = await self.notifier.send_notification(
self.message, line_token=self.line_token,
)
self.assertEqual(status_code, 200)
mock_post.assert_called_once_with(
'https://notify-api.line.me/api/notify',
headers={'Authorization': f'Bearer {self.line_token}'},
params={'message': self.message},
data=None,
mock_post.assert_called_once()
args, kwargs = mock_post.call_args
self.assertIn('data', kwargs)
self._assert_field_exists(
kwargs['data']._fields, 'message', self.message,
)

@patch('aiohttp.ClientSession.post')
async def test_send_notification_with_image(
self, mock_post: MagicMock,
) -> None:
"""
Test case for sending notification with an image as a NumPy array.
Tests sending a notification with an image provided as a NumPy array.
Args:
mock_post (MagicMock): Mock object for aiohttp.ClientSession.post.
"""
mock_response: MagicMock = MagicMock()
mock_response.status = 200
mock_response.status = 200 # Simulate a successful response
mock_post.return_value.__aenter__.return_value = mock_response

status_code: int = await self.notifier.send_notification(
self.message,
self.image,
line_token=self.line_token,
self.message, self.image, line_token=self.line_token,
)
self.assertEqual(status_code, 200)
mock_post.assert_called_once()
args, kwargs = mock_post.call_args
self.assertIn('data', kwargs)
self.assertIn('imageFile', kwargs['data'])

# 檢查 imageFile
image_file = kwargs['data']['imageFile']
self.assertEqual(image_file.name, 'image.png')
self.assertEqual(image_file.content_type, 'image/png')
image_file.seek(0)
image: Image.Image = Image.open(image_file)
self.assertTrue(np.array_equal(np.array(image), self.image))
self._assert_field_exists(
kwargs['data']._fields, 'message', self.message,
)
self._assert_field_exists(kwargs['data']._fields, 'imageFile', None)

@patch('aiohttp.ClientSession.post')
async def test_send_notification_with_bytes_image(
self,
mock_post: MagicMock,
self, mock_post: MagicMock,
) -> None:
"""
Test case for sending notification
with an image as bytes (e.g., BytesIO).
Tests sending a notification with an image provided as bytes.
Args:
mock_post (MagicMock): Mock object for aiohttp.ClientSession.post.
"""
mock_response: MagicMock = MagicMock()
mock_response.status = 200
mock_response.status = 200 # Simulate a successful response
mock_post.return_value.__aenter__.return_value = mock_response

# Convert the NumPy array image to bytes
buffer: BytesIO = BytesIO()
Image.fromarray(self.image).save(buffer, format='PNG')
buffer.seek(0)
Expand All @@ -140,36 +147,68 @@ async def test_send_notification_with_bytes_image(
mock_post.assert_called_once()
args, kwargs = mock_post.call_args
self.assertIn('data', kwargs)
self.assertIn('imageFile', kwargs['data'])

# Check if the image is correctly converted and sent
image_file = kwargs['data']['imageFile']
self.assertEqual(image_file.name, 'image.png')
self.assertEqual(image_file.content_type, 'image/png')
image: Image.Image = Image.open(image_file)
self.assertTrue(np.array_equal(np.array(image), self.image))
self._assert_field_exists(
kwargs['data']._fields, 'message', self.message,
)
self._assert_field_exists(kwargs['data']._fields, 'imageFile', None)

@patch('aiohttp.ClientSession.post')
async def test_main(self, mock_post: MagicMock) -> None:
"""
Test the main function to ensure the complete process is covered.
Tests the main function to ensure the entire process works as expected.
Args:
mock_post (MagicMock): Mock object for aiohttp.ClientSession.post.
"""
mock_response: MagicMock = MagicMock()
mock_response.status = 200
mock_response.status = 200 # Simulate a successful response
mock_post.return_value.__aenter__.return_value = mock_response

with patch('builtins.print') as mock_print:
await main()
mock_print.assert_called_once_with('Response code: 200')

def _assert_field_exists(
self, fields: list, field_name: str, expected_value: str | None = None,
) -> None:
"""
Helper method to assert that a field exists in FormData fields.
Args:
fields (list): List of FormData fields.
field_name (str): The name of the field to check for.
expected_value (str | None):
The expected value of the field (optional).
Raises:
AssertionError: If the field is not found
or does not match the expected value.
"""
found = False
for field, _, value in fields:
if field.get('name') == field_name:
found = True
if expected_value is not None:
self.assertEqual(value, expected_value)
break
self.assertTrue(
found, f"Field '{field_name}' not found in FormData fields.",
)

@patch.dict(os.environ, {'LINE_NOTIFY_TOKEN': 'test_token'})
@patch('aiohttp.ClientSession.post')
async def test_main_as_script(self, mock_post: MagicMock) -> None:
"""
Tests running the main function as a standalone script.
Args:
mock_post (MagicMock): Mock object for aiohttp.ClientSession.post.
"""
mock_response: MagicMock = MagicMock()
mock_response.status = 200
mock_response.status = 200 # Simulate a successful response
mock_post.return_value.__aenter__.return_value = mock_response

# Get the absolute path to the line_notifier.py script
# Get the absolute path to the script
script_path = os.path.abspath(
os.path.join(
os.path.dirname(
Expand All @@ -180,19 +219,10 @@ async def test_main_as_script(self, mock_post: MagicMock) -> None:

# Run the script using subprocess
result = subprocess.run(
['python', script_path],
capture_output=True, text=True,
['python', script_path], capture_output=True, text=True,
)

# Print stderr and stdout for debugging
print('STDOUT:', result.stdout)
print('STDERR:', result.stderr)

# Assert that the script runs without errors
self.assertEqual(
result.returncode, 0,
'Script exited with a non-zero status.',
)
self.assertEqual(result.returncode, 0)


if __name__ == '__main__':
Expand Down

0 comments on commit 0fcf27f

Please sign in to comment.