-
-
Notifications
You must be signed in to change notification settings - Fork 38.2k
Add Facebox teach service #14998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Facebox teach service #14998
Changes from 21 commits
0a85a53
c9d68f3
d503b4c
f529b7d
d9292a7
d4ef2ff
2eed0d9
8b1791e
a1751ec
4664c93
d87bddd
0179d37
863ebd6
851b970
182be06
4ba767f
8c8f413
74f7f0b
cf6497a
6a23b19
5bf7a13
49a33f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| """The tests for the facebox component.""" | ||
| from unittest.mock import patch | ||
| from unittest.mock import Mock, patch | ||
|
|
||
| import pytest | ||
| import requests | ||
|
|
@@ -17,18 +17,23 @@ | |
| MOCK_PORT = '8080' | ||
|
|
||
| # Mock data returned by the facebox API. | ||
| MOCK_ERROR = "No face found" | ||
| MOCK_FACE = {'confidence': 0.5812028911604818, | ||
| 'id': 'john.jpg', | ||
| 'matched': True, | ||
| 'name': 'John Lennon', | ||
| 'rect': {'height': 75, 'left': 63, 'top': 262, 'width': 74} | ||
| } | ||
|
|
||
| MOCK_FILE_PATH = '/images/mock.jpg' | ||
|
|
||
| MOCK_JSON = {"facesCount": 1, | ||
| "success": True, | ||
| "faces": [MOCK_FACE] | ||
| } | ||
|
|
||
| MOCK_NAME = 'mock_name' | ||
|
|
||
| # Faces data after parsing. | ||
| PARSED_FACES = [{ATTR_NAME: 'John Lennon', | ||
| fb.ATTR_IMAGE_ID: 'john.jpg', | ||
|
|
@@ -63,11 +68,20 @@ def test_encode_image(): | |
| assert fb.encode_image(b'test') == 'dGVzdA==' | ||
|
|
||
|
|
||
| def test_get_matched_faces(): | ||
| """Test that matched_faces are parsed correctly.""" | ||
| assert fb.get_matched_faces(PARSED_FACES) == MATCHED_FACES | ||
|
|
||
|
|
||
| def test_parse_faces(): | ||
| """Test parsing of raw face data, and generation of matched_faces.""" | ||
| parsed_faces = fb.parse_faces(MOCK_JSON['faces']) | ||
| assert parsed_faces == PARSED_FACES | ||
| assert fb.get_matched_faces(parsed_faces) == MATCHED_FACES | ||
| assert fb.parse_faces(MOCK_JSON['faces']) == PARSED_FACES | ||
|
|
||
|
|
||
| @patch('os.access', Mock(return_value=False)) | ||
| def test_valid_file_path(): | ||
| """Test that an invalid file_path is caught.""" | ||
| assert not fb.valid_file_path('test_path') | ||
|
|
||
|
|
||
| @pytest.fixture | ||
|
|
@@ -110,6 +124,7 @@ def mock_face_event(event): | |
| state = hass.states.get(VALID_ENTITY_ID) | ||
| assert state.state == '1' | ||
| assert state.attributes.get('matched_faces') == MATCHED_FACES | ||
| assert state.attributes.get('total_matched_faces') == 1 | ||
|
|
||
| PARSED_FACES[0][ATTR_ENTITY_ID] = VALID_ENTITY_ID # Update. | ||
| assert state.attributes.get('faces') == PARSED_FACES | ||
|
|
@@ -147,6 +162,61 @@ async def test_connection_error(hass, mock_image): | |
| assert state.attributes.get('matched_faces') == {} | ||
|
|
||
|
|
||
| @patch('os.access', Mock(return_value=True)) | ||
| @patch('os.path.isfile', Mock(return_value=True)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a problem with this test. It's never awaited. Unfortunately that passes silently. I think it's because of these patch decorators. I noticed if after checking the coverage report. |
||
| async def test_teach_service(hass, mock_image): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is never awaited. Please run the tests locally, eg with |
||
| """Test teaching of facebox.""" | ||
| await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) | ||
| assert hass.states.get(VALID_ENTITY_ID) | ||
|
|
||
| teach_events = [] | ||
|
|
||
| @callback | ||
| def mock_teach_event(event): | ||
| """Mock event.""" | ||
| teach_events.append(event) | ||
|
|
||
| hass.bus.async_listen( | ||
| 'image_processing.teach_classifier', mock_teach_event) | ||
|
|
||
| with requests_mock.Mocker() as mock_req: | ||
| url = "http://{}:{}/facebox/teach".format(MOCK_IP, MOCK_PORT) | ||
| mock_req.post(url, status_code=200) | ||
| data = {ATTR_ENTITY_ID: VALID_ENTITY_ID, | ||
| ATTR_NAME: MOCK_NAME, | ||
| fb.FILE_PATH: MOCK_FILE_PATH} | ||
| await hass.services.async_call(ip.DOMAIN, | ||
| fb.SERVICE_TEACH_FACE, | ||
| service_data=data) | ||
| await hass.async_block_till_done() | ||
|
|
||
| assert len(teach_events) == 1 | ||
| assert teach_events[0].data[fb.ATTR_CLASSIFIER] == fb.CLASSIFIER | ||
| assert teach_events[0].data[ATTR_NAME] == MOCK_NAME | ||
| assert teach_events[0].data[fb.FILE_PATH] == MOCK_FILE_PATH | ||
| assert teach_events[0].data['success'] | ||
| assert not teach_events[0].data['message'] | ||
|
|
||
| # Now test the failed teaching. | ||
| with requests_mock.Mocker() as mock_req: | ||
| url = "http://{}:{}/facebox/teach".format(MOCK_IP, MOCK_PORT) | ||
| mock_req.post(url, status_code=400, text=MOCK_ERROR) | ||
| data = {ATTR_ENTITY_ID: VALID_ENTITY_ID, | ||
| ATTR_NAME: MOCK_NAME, | ||
| fb.FILE_PATH: MOCK_FILE_PATH} | ||
| await hass.services.async_call(ip.DOMAIN, | ||
| fb.SERVICE_TEACH_FACE, | ||
| service_data=data) | ||
| await hass.async_block_till_done() | ||
|
|
||
| assert len(teach_events) == 2 | ||
| assert teach_events[1].data[fb.ATTR_CLASSIFIER] == fb.CLASSIFIER | ||
| assert teach_events[1].data[ATTR_NAME] == MOCK_NAME | ||
| assert teach_events[1].data[fb.FILE_PATH] == MOCK_FILE_PATH | ||
| assert not teach_events[1].data['success'] | ||
| assert teach_events[1].data['message'] == MOCK_ERROR | ||
|
|
||
|
|
||
| async def test_setup_platform_with_name(hass): | ||
| """Setup platform with one entity and a name.""" | ||
| MOCK_NAME = 'mock_name' | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this our standard format for event names? Have you checked other platforms or components that fire events?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a consistent format, so took my lead from
EVENT_DETECT_FACE = 'image_processing.detect_face'. I thinkteach_classifieris pretty a accurate description, but am open to other suggestions?