diff --git a/cookbook/connectors/connector_manager.py b/cookbook/connectors/connector_manager.py index 53252307..d660b6d8 100644 --- a/cookbook/connectors/connector_manager.py +++ b/cookbook/connectors/connector_manager.py @@ -28,7 +28,7 @@ class ActionType(Enum): @dataclass -class Payload: +class Work: instance: REGISTERED_CLASSES actionType: ActionType @@ -57,7 +57,7 @@ class ConnectorManager: return try: - self._queue.put_nowait(Payload(instance, action_type)) + self._queue.put_nowait(Work(instance, action_type)) except queue.Full: return @@ -66,7 +66,7 @@ class ConnectorManager: self._worker.join() @staticmethod - def worker(queue: Queue): + def worker(worker_queue: Queue): from django.db import connections connections.close_all() @@ -76,7 +76,7 @@ class ConnectorManager: _connectors: Dict[str, List[Connector]] = dict() while True: - item: Optional[Payload] = queue.get() + item: Optional[Work] = worker_queue.get() if item is None: break @@ -119,4 +119,4 @@ async def run_connectors(connectors: List[Connector], space: Space, instance: RE try: await asyncio.gather(*tasks, return_exceptions=False) except BaseException: - logging.exception("received an exception from one of the tasks") + logging.exception("received an exception from one of the connectors") diff --git a/cookbook/tests/api/test_api_home_assistant_config.py b/cookbook/tests/api/test_api_home_assistant_config.py new file mode 100644 index 00000000..a3de0f45 --- /dev/null +++ b/cookbook/tests/api/test_api_home_assistant_config.py @@ -0,0 +1,126 @@ +import json + +import pytest +from django.contrib import auth +from django.urls import reverse +from django_scopes import scopes_disabled + +from cookbook.models import HomeAssistantConfig + +LIST_URL = 'api:homeassistantconfig-list' +DETAIL_URL = 'api:homeassistantconfig-detail' + + +@pytest.fixture() +def obj_1(space_1, u1_s1): + return HomeAssistantConfig.objects.create( + name='HomeAssistant 1', token='token', url='url', todo_entity='todo.shopping_list', enabled=True, created_by=auth.get_user(u1_s1), space=space_1, ) + + +@pytest.fixture +def obj_2(space_1, u1_s1): + return HomeAssistantConfig.objects.create( + name='HomeAssistant 2', token='token', url='url', todo_entity='todo.shopping_list', enabled=True, created_by=auth.get_user(u1_s1), space=space_1, ) + + +@pytest.mark.parametrize( + "arg", [ + ['a_u', 403], + ['g1_s1', 403], + ['u1_s1', 403], + ['a1_s1', 200], + ]) +def test_list_permission(arg, request): + c = request.getfixturevalue(arg[0]) + r = c.get(reverse(LIST_URL)) + assert r.status_code == arg[1] + if r.status_code == 200: + response = json.loads(r.content) + assert 'token' not in response + + +def test_list_space(obj_1, obj_2, a1_s1, a1_s2, space_2): + assert len(json.loads(a1_s1.get(reverse(LIST_URL)).content)) == 2 + assert len(json.loads(a1_s2.get(reverse(LIST_URL)).content)) == 0 + + obj_1.space = space_2 + obj_1.save() + + assert len(json.loads(a1_s1.get(reverse(LIST_URL)).content)) == 1 + assert len(json.loads(a1_s2.get(reverse(LIST_URL)).content)) == 1 + + +@pytest.mark.parametrize( + "arg", [ + ['a_u', 403], + ['g1_s1', 403], + ['u1_s1', 403], + ['a1_s1', 200], + ['g1_s2', 403], + ['u1_s2', 403], + ['a1_s2', 404], + ]) +def test_update(arg, request, obj_1): + test_token = '1234' + + c = request.getfixturevalue(arg[0]) + r = c.patch( + reverse( + DETAIL_URL, + args={obj_1.id} + ), + {'name': 'new', 'token': test_token}, + content_type='application/json' + ) + response = json.loads(r.content) + assert r.status_code == arg[1] + if r.status_code == 200: + assert response['name'] == 'new' + obj_1.refresh_from_db() + assert obj_1.token == test_token + + +@pytest.mark.parametrize( + "arg", [ + ['a_u', 403], + ['g1_s1', 403], + ['u1_s1', 403], + ['a1_s1', 201], + ]) +def test_add(arg, request, a1_s2, obj_1): + c = request.getfixturevalue(arg[0]) + r = c.post( + reverse(LIST_URL), + {'name': 'test', 'url': 'http://localhost:8123/api', 'token': '1234', 'enabled': 'true'}, + content_type='application/json' + ) + response = json.loads(r.content) + print(r.content) + assert r.status_code == arg[1] + if r.status_code == 201: + assert response['name'] == 'test' + r = c.get(reverse(DETAIL_URL, args={response['id']})) + assert r.status_code == 200 + r = a1_s2.get(reverse(DETAIL_URL, args={response['id']})) + assert r.status_code == 404 + + +def test_delete(a1_s1, a1_s2, obj_1): + r = a1_s2.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + assert r.status_code == 404 + + r = a1_s1.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + + assert r.status_code == 204 + with scopes_disabled(): + assert HomeAssistantConfig.objects.count() == 0 diff --git a/cookbook/tests/edits/test_edits_home_assistant_config.py b/cookbook/tests/edits/test_edits_home_assistant_config.py new file mode 100644 index 00000000..053972b6 --- /dev/null +++ b/cookbook/tests/edits/test_edits_home_assistant_config.py @@ -0,0 +1,58 @@ +from cookbook.models import Storage, HomeAssistantConfig +from django.contrib import auth +from django.urls import reverse +import pytest + +EDIT_VIEW_NAME = 'edit_home_assistant_config' + + +@pytest.fixture +def home_assistant_config_obj(a1_s1, space_1): + return HomeAssistantConfig.objects.create( + name='HomeAssistant 1', + token='token', + url='url', + todo_entity='todo.shopping_list', + enabled=True, + created_by=auth.get_user(a1_s1), + space=space_1, + ) + + +def test_edit_home_assistant_config(home_assistant_config_obj, a1_s1, a1_s2): + new_token = '1234_token' + + r = a1_s1.post( + reverse(EDIT_VIEW_NAME, args={home_assistant_config_obj.pk}), + { + 'name': 'HomeAssistant 1', + 'token': new_token, + } + ) + home_assistant_config_obj.refresh_from_db() + assert r.status_code == 200 + assert home_assistant_config_obj.token == new_token + + r = a1_s2.post( + reverse(EDIT_VIEW_NAME, args={home_assistant_config_obj.pk}), + { + 'name': 'HomeAssistant 1', + 'token': new_token, + } + ) + assert r.status_code == 404 + + +@pytest.mark.parametrize( + "arg", [ + ['a_u', 302], + ['g1_s1', 302], + ['u1_s1', 302], + ['a1_s1', 200], + ['g1_s2', 302], + ['u1_s2', 302], + ['a1_s2', 404], + ]) +def test_view_permission(arg, request, home_assistant_config_obj): + c = request.getfixturevalue(arg[0]) + assert c.get(reverse(EDIT_VIEW_NAME, args={home_assistant_config_obj.pk})).status_code == arg[1] diff --git a/cookbook/tests/other/test_connector_manager.py b/cookbook/tests/other/test_connector_manager.py new file mode 100644 index 00000000..c6df778f --- /dev/null +++ b/cookbook/tests/other/test_connector_manager.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass + +import pytest +from django.contrib import auth +from mock.mock import Mock + +from cookbook.connectors.connector import Connector +from cookbook.connectors.connector_manager import run_connectors, ActionType +from cookbook.models import ShoppingListEntry, ShoppingList, Food + + +@pytest.fixture() +def obj_1(space_1, u1_s1): + e = ShoppingListEntry.objects.create(created_by=auth.get_user(u1_s1), food=Food.objects.get_or_create(name='test 1', space=space_1)[0], space=space_1) + s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, ) + s.entries.add(e) + return e + + +@pytest.mark.asyncio +async def test_run_connectors(space_1, u1_s1, obj_1) -> None: + connector_mock = Mock(spec=Connector) + + await run_connectors([connector_mock], space_1, obj_1, ActionType.DELETED) + + assert not connector_mock.on_shopping_list_entry_updated.called + assert not connector_mock.on_shopping_list_entry_created.called + connector_mock.on_shopping_list_entry_deleted.assert_called_once_with(space_1, obj_1) diff --git a/requirements.txt b/requirements.txt index 0611ea42..23789d2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,6 +33,7 @@ django-allauth==0.58.1 recipe-scrapers==14.52.0 django-scopes==2.0.0 pytest==7.4.3 +pytest-asyncio==0.23.3 pytest-django==4.6.0 django-treebeard==4.7 django-cors-headers==4.2.0