diff --git a/.gitignore b/.gitignore index 23d0c16..c328bb9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ bin/ lib/ .tox/* *.egg-info +.eggs/ diff --git a/logging.conf b/logging.conf new file mode 100644 index 0000000..2d13c5a --- /dev/null +++ b/logging.conf @@ -0,0 +1,28 @@ +[loggers] +keys=console,file + +[handlers] +keys=console_handler + +[formatters] +keys=simple + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[logger_simpleExample] +level=DEBUG +handlers=consoleHandler +qualname=simpleExample +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s +datefmt= diff --git a/tests/test_grocy.py b/tests/test_grocy.py index 1f9ce4c..570d0dc 100644 --- a/tests/test_grocy.py +++ b/tests/test_grocy.py @@ -34,6 +34,20 @@ def setup_grocy_args(): } return grocy_args +class mockTW(object): + def task_add(self, **kwargs): + pass + def task_update(self, **kwargs): + pass + +@pytest.fixture(scope="function") +def setup_tw(): + tw = mockTw() + return tw + + + + def test_get_category(mocker, setup_grocy_args): expected_category = setup_grocy_args['category'] @@ -99,11 +113,178 @@ def test_to_grocy(mocker, setup_grocy_args): Grocy.DUE_DATE: expected_task[TaskService.DUE].strftime('%Y-%m-%d'), Grocy.DONE: expected_task[TaskService.DONE] } - print('expected_grocy_task {}'.format(expected_grocy_task)) Grocy.get_category.return_value = {'name': setup_grocy_args['category']} grocy = Grocy(**setup_grocy_args) actual_grocy_task = grocy.to_grocy(expected_task) - print('actual_grocy_task {}'.format(actual_grocy_task)) assert expected_grocy_task == actual_grocy_task +def test_to_taskwarrior(mocker, setup_grocy_args): + mocker.patch.object(Grocy, 'get_category') + notes_arr = ['This is line1', 'This is line2'] + notes_string = '' + entry_time = datetime.time(18,22,00) + entry_date = datetime.date(2019,1,16) + due_time = datetime.time(18,26,00) + due_date = datetime.date(2019,1,20) + for note in notes_arr: + notes_string+= '{}\n'.format(note) + + expected_grocy_task = { + Grocy.ID : '1337', + Grocy.UDA_GROCY_TW_UUID: 'c9ce6ba3-973a-49d7-b7bd-4ff88ee464d9', + Grocy.ASSIGNED_TO_USER_ID: '1', + Grocy.NAME: 'This is a task from grocy', + Grocy.CATEGORY: setup_grocy_args['category'], + Grocy.DESCRIPTION: notes_string, + Grocy.ROW_CREATED_TIMESTAMP: datetime.datetime.combine(entry_date,entry_time), + Grocy.DUE_DATE: datetime.datetime.combine(entry_date,entry_time), + Grocy.DONE: '1' + } + expected_task = { + Grocy.UDA_GROCY_ID: expected_grocy_task[Grocy.ID], + Grocy.UDA_GROCY_ASSIGNED_TO_USER_ID : expected_grocy_task[Grocy.ASSIGNED_TO_USER_ID], + TaskService.UUID: expected_grocy_task[Grocy.UDA_GROCY_TW_UUID], + TaskService.DESCRIPTION: expected_grocy_task[Grocy.NAME], + TaskService.TAGS : [setup_grocy_args['category']], + TaskService.ANNOTATIONS: notes_arr, + TaskService.ENTRY: expected_grocy_task[Grocy.ROW_CREATED_TIMESTAMP], + TaskService.DUE: expected_grocy_task[Grocy.DUE_DATE], + TaskService.STATUS: TaskService.COMPLETED + } + + Grocy.get_category.return_value = {'name': setup_grocy_args['category']} + grocy = Grocy(**setup_grocy_args) + actual_task = grocy.to_taskwarrior(expected_grocy_task) + assert expected_task == actual_task + +def test_find_all(mocker, setup_grocy_args): + mocker.patch.object(RestService, '__init__') + mocker.patch.object(RestService, 'get') + mocker.patch.object(RestService, 'addHeader') + expected_grocy_tasks = [ + { 'id': 1, 'name': 'This is a grocy task 1' }, + { 'id': 2, 'name': 'This is a grocy task 2' } + ] + RestService.get.return_value = expected_grocy_tasks + RestService.__init__.return_value = None + grocy = Grocy(**setup_grocy_args) + actual_grocy_tasks = grocy.find_all() + + RestService.addHeader.assert_called_with(Grocy.API_KEY_HEADER, setup_grocy_args['token']) + RestService.get.assert_called_with(Grocy.FIND_ALL_TASKS_ENDPOINT) + assert actual_grocy_tasks == expected_grocy_tasks + +def test_delete_success(mocker, setup_grocy_args): + mocker.patch.object(RestService, '__init__') + mocker.patch.object(RestService, 'get') + mocker.patch.object(RestService, 'addHeader') + expected_grocy_task_id = 1 + expected_delete_response = { 'success': True, 'error_message': '' } + RestService.get.return_value = expected_delete_response + RestService.__init__.return_value = None + grocy = Grocy(**setup_grocy_args) + actual_delete_response = grocy.delete(expected_grocy_task_id) + + RestService.addHeader.assert_called_with(Grocy.API_KEY_HEADER, setup_grocy_args['token']) + RestService.get.assert_called_with(Grocy.DELETE_TASK_ENDPOINT, expected_grocy_task_id) + assert actual_delete_response == expected_delete_response + +def test_add_success_single(mocker, setup_grocy_args): + mocker.patch.object(RestService, '__init__') + mocker.patch.object(RestService, 'post') + mocker.patch.object(RestService, 'addHeader') + expected_grocy_task = { 'id': 1, 'name': 'This is a grocy task 1' }, + expected_grocy_tasks = [] + expected_grocy_tasks.append(expected_grocy_task) + + RestService.post.return_value = expected_grocy_task + RestService.__init__.return_value = None + grocy = Grocy(**setup_grocy_args) + actual_grocy_tasks = grocy.add(expected_grocy_task) + + RestService.addHeader.assert_called_with(Grocy.API_KEY_HEADER, setup_grocy_args['token']) + RestService.post.assert_called_with(Grocy.ADD_TASK_ENDPOINT, expected_grocy_task) + assert type(actual_grocy_tasks) == list + assert len(actual_grocy_tasks) == 1 + assert actual_grocy_tasks == expected_grocy_tasks + +def test_modify_success_single(mocker, setup_grocy_args): + mocker.patch.object(RestService, '__init__') + mocker.patch.object(RestService, 'post') + mocker.patch.object(RestService, 'addHeader') + expected_modify_responses = [] + expected_modify_response = { 'success': True, 'error_message': '' } + expected_modify_responses.append(expected_modify_response) + expected_modify_grocy_task_id = 1 + expected_modify_endpoint = '{0}/{1}'.format(Grocy.MODIFY_TASK_ENDPOINT, expected_modify_grocy_task_id) + expected_modify_grocy_task = { 'id': expected_modify_grocy_task_id, 'name': 'This is a modified grocy task 1' } + expected_modify_grocy_tasks = [] + expected_modify_grocy_tasks.append(expected_modify_grocy_task) + + RestService.post.return_value = expected_modify_response + RestService.__init__.return_value = None + grocy = Grocy(**setup_grocy_args) + actual_modify_responses = grocy.modify(expected_modify_grocy_tasks) + + RestService.addHeader.assert_called_with(Grocy.API_KEY_HEADER, setup_grocy_args['token']) + RestService.post.assert_called_with(expected_modify_endpoint, expected_modify_grocy_task) + assert type(actual_modify_responses) == list + assert len(actual_modify_responses) == 1 + assert actual_modify_responses == expected_modify_responses + +#def test_merge_from_tw(mocker, setup_grocy_args, setup_tw): +# notes_string = '' +# updated_string = 'This is a new note' +# notes_arr = ['This is line1', 'This is line2'] +# updated_arr = deepclone(notes_arr) +# updated_arr.append(updated_string) +# for note in notes_arr: +# notes_string+= '{}\n'.format(note) +# updated_notes_string+='This is a new note\n' +# mocker.patch.object(Grocy, 'to_grocy') +# mocker.patch.object(Grocy, 'to_taskwarrior') +# mocker.patch.object(mockTw, 'task_add') +# grocy_tasks = [{ +# Grocy.ID : '1337', +# Grocy.UDA_GROCY_TW_UUID: 'c9ce6ba3-973a-49d7-b7bd-4ff88ee464d9', +# Grocy.ASSIGNED_TO_USER_ID: '1', +# Grocy.NAME: 'This is a new task from grocy', +# Grocy.CATEGORY: setup_grocy_args['category'], +# Grocy.DESCRIPTION: updated_notes_string, +# Grocy.ROW_CREATED_TIMESTAMP: datetime.datetime.combine(entry_date,entry_time), +# Grocy.DUE_DATE: datetime.datetime.combine(entry_date,entry_time), +# Grocy.DONE: '1' +# }] +# tw_tasks = [{ +# Grocy.UDA_GROCY_ID: grocy_tasks[0][Grocy.ID], +# Grocy.UDA_GROCY_ASSIGNED_TO_USER_ID : '2', +# TaskService.UUID: grocy_tasks[0][Grocy.UDA_GROCY_TW_UUID] +# TaskService.DESCRIPTION: 'This was the old task from grocy' +# TaskService.TAGS : [setup_grocy_args['category']], +# TaskService.ANNOTATIONS: notes_arr, +# TaskService.ENTRY: expected_grocy_task[Grocy.ROW_CREATED_TIMESTAMP], +# TaskService.DUE: expected_grocy_task[Grocy.DUE_DATE], +# TaskService.STATUS: TaskService.COMPLETED +# }] +# +# expected_modify_responses = [] +# expected_modify_response = { 'success': True, 'error_message': '' } +# expected_modify_responses.append(expected_modify_response) +# expected_modify_grocy_task_id = 1 +# expected_modify_endpoint = '{0}/{1}'.format(Grocy.MODIFY_TASK_ENDPOINT, expected_modify_grocy_task_id) +# expected_modify_grocy_task = { 'id': expected_modify_grocy_task_id, 'name': 'This is a modified grocy task 1' } +# expected_modify_grocy_tasks = [] +# expected_modify_grocy_tasks.append(expected_modify_grocy_task) +# +# RestService.post.return_value = expected_modify_response +# RestService.__init__.return_value = None +# grocy = Grocy(**setup_grocy_args) +# actual_modify_responses = grocy.modify(expected_modify_grocy_tasks) +# +# RestService.addHeader.assert_called_with(Grocy.API_KEY_HEADER, setup_grocy_args['token']) +# RestService.post.assert_called_with(expected_modify_endpoint, expected_modify_grocy_task) +# assert type(actual_modify_responses) == list +# assert len(actual_modify_responses) == 1 +# assert actual_modify_responses == expected_modify_responses + diff --git a/twservices/apps/__init__.py b/twservices/apps/__init__.py index 2d924bf..5c0d0f7 100644 --- a/twservices/apps/__init__.py +++ b/twservices/apps/__init__.py @@ -9,7 +9,9 @@ class TaskService(object): DESCRIPTION = 'description' TAGS = 'tags' ANNOTATIONS = 'annotations' + STATUS = 'status' ENTRY = 'entry' + COMPLETED = 'completed' DONE = 'done' DUE = 'due' diff --git a/twservices/apps/grocy.py b/twservices/apps/grocy.py index 019e7f6..4853fbf 100644 --- a/twservices/apps/grocy.py +++ b/twservices/apps/grocy.py @@ -13,6 +13,7 @@ class Grocy(TaskService): # PROPS ID = 'id' DONE = 'done' + DESCRIPTION = 'description' ASSIGNED_TO_USER_ID = 'assigned_to_user_id' ROW_CREATED_TIMESTAMP = 'row_created_timestamp' CATEGORY = 'category' @@ -155,39 +156,38 @@ class Grocy(TaskService): def to_taskwarrior(self, grocy_task): taskwarrior_task = {} - if 'id' in grocy_task: - taskwarrior_task[Grocy.UDA_GROCY_ID] = grocy_task['id'] + if Grocy.ID in grocy_task: + taskwarrior_task[Grocy.UDA_GROCY_ID] = grocy_task[Grocy.ID] if Grocy.UDA_GROCY_TW_UUID in grocy_task: - taskwarrior_task['uuid'] = grocy_task[Grocy.UDA_GROCY_TW_UUID] + taskwarrior_task[TaskService.UUID] = grocy_task[Grocy.UDA_GROCY_TW_UUID] if Grocy.ASSIGNED_TO_USER_ID in grocy_task: taskwarrior_task[Grocy.UDA_GROCY_ASSIGNED_TO_USER_ID] = grocy_task[Grocy.ASSIGNED_TO_USER_ID] - if 'name' in grocy_task: - taskwarrior_task['description'] = grocy_task['name'] + if Grocy.NAME in grocy_task: + taskwarrior_task[TaskService.DESCRIPTION] = grocy_task[Grocy.NAME] - if 'description' in grocy_task: - if '\r' in grocy_task['description']: - grocy_task['description'] = grocy_task['description'].replace('\r', '') - taskwarrior_task['annotations'] = grocy_task['description'].split('\n') - last_element_is_empty_string = taskwarrior_task['annotations'][-1] == '' + if Grocy.DESCRIPTION in grocy_task: + if '\r' in grocy_task[Grocy.DESCRIPTION]: + grocy_task[Grocy.DESCRIPTION] = grocy_task[Grocy.DESCRIPTION].replace('\r', '') + taskwarrior_task[TaskService.ANNOTATIONS] = grocy_task[Grocy.DESCRIPTION].split('\n') + last_element_is_empty_string = taskwarrior_task[TaskService.ANNOTATIONS][-1] == '' if last_element_is_empty_string: - taskwarrior_task['annotations'] = taskwarrior_task['annotations'][0:-1] # Remove empty string in array + taskwarrior_task[TaskService.ANNOTATIONS] = taskwarrior_task[TaskService.ANNOTATIONS][0:-1] # Remove empty string in array - if 'row_created_timestamp' in grocy_task: - taskwarrior_task['entry'] = grocy_task['row_created_timestamp'] + if Grocy.ROW_CREATED_TIMESTAMP in grocy_task: + taskwarrior_task[TaskService.ENTRY] = grocy_task[Grocy.ROW_CREATED_TIMESTAMP] - if 'due_date' in grocy_task: - taskwarrior_task['due'] = grocy_task['due_date'] + if Grocy.DUE_DATE in grocy_task: + taskwarrior_task[TaskService.DUE] = grocy_task[Grocy.DUE_DATE] - if 'done' in grocy_task and grocy_task['done'] == '1': - taskwarrior_task['status'] = 'completed' + if Grocy.DONE in grocy_task and grocy_task[Grocy.DONE] == '1': + taskwarrior_task[TaskService.STATUS] = TaskService.COMPLETED - if 'category_id' in grocy_task: + if Grocy.CATEGORY in grocy_task: category = self.get_category() - print('category {}'.format(category)) if category: - taskwarrior_task['tags'] = [ category['name'] ] + taskwarrior_task[TaskService.TAGS] = [ category['name'] ] return taskwarrior_task @@ -200,16 +200,16 @@ class Grocy(TaskService): rest_service.addHeader(Grocy.API_KEY_HEADER, self.token) return rest_service - def findAll(self): + def find_all(self): if not self.rest_service: raise Exception('rest service for grocy is not defined') # Get all tasks as json try: - response = self.rest_service.get(Grocy.FIND_ALL_TASKS_ENDPOINT) + responses = self.rest_service.get(Grocy.FIND_ALL_TASKS_ENDPOINT) except BaseException as e: logger.error(e) raise e - return response + return responses def delete(self, id): if not self.rest_service: @@ -219,49 +219,57 @@ class Grocy(TaskService): # Delete a task try: response = self.rest_service.get(Grocy.DELETE_TASK_ENDPOINT, id) + if response and response['success']: + return response + elif response and not response['success']: + raise Exception(response['error']) + else: + raise Exception('Grocy service unavailable Service') except Exception as e: logger.error(e) raise e - return response - def add(self, task): + def add(self, grocy_task): if not self.rest_service: raise Exception('rest service for grocy is not defined') responses = [] - if not task: - raise Exception('task is not defined') - if not type(task) is list: - tasks = [task] + if not grocy_task: + raise Exception('grocy_task is not defined') + if not type(grocy_task) is list: + grocy_tasks = [grocy_task] else: - tasks = task + grocy_tasks = grocy_task try: - for next_task in tasks: - self.to_grocy(next_task) - response = self.rest_service.post(Grocy.ADD_TASK_ENDPOINT, task) + for next_grocy_task in grocy_tasks: + response = self.rest_service.post(Grocy.ADD_TASK_ENDPOINT, next_grocy_task) responses.append(response) except Exception as e: logger.error(e) raise e return responses - def modify(self, task): + def modify(self, grocy_task): if not self.rest_service: raise Exception('rest service for grocy is not defined') - response = [] - if not task: - raise Exception('task is not defined') - if not type(task) is list: - tasks = [task] + responses = [] + if not grocy_task: + raise Exception('grocy_task is not defined') + if not type(grocy_task) is list: + grocy_tasks = [grocy_task] else: - tasks = task + grocy_tasks = grocy_task try: - for next_task in tasks: - modify_endpoint = '{0}/{1}'.format(Grocy.MODIFY_TASK_ENDPOINT, next_task['id']) - response = self.rest_service.post(modify_endpoint, next_task) + for grocy_next_task in grocy_tasks: + modify_endpoint = '{0}/{1}'.format(Grocy.MODIFY_TASK_ENDPOINT, grocy_next_task['id']) + response = self.rest_service.post(modify_endpoint, grocy_next_task) + responses.append(response) except Exception as e: - logger.exception('Could not send post to modify grocy task %s', e.stderr) + logger.exception('Could not send post to modify grocy grocy_task %s', e.stderr) + raise e + + return responses def _should_merge(self, converted_tw_to_grocy_task, grocy_task): return True if converted_tw_to_grocy_task['id'] == grocy_task['id'] else False @@ -380,17 +388,17 @@ class Grocy(TaskService): taskwarrior_tasks = self.get_grocy_tw_tasks() try: - grocy_tasks = self.findAll() + grocy_tasks = self.find_all() except BaseException as e: logger.error(e) logger.error('Could not get all grocy tasks') try: if self.resolution == 'tw': - grocy_tasks = self.findAll() + grocy_tasks = self.find_all() self.merge_from_tw(taskwarrior_tasks, grocy_tasks) elif self.resolution == 'grocy': - grocy_tasks = self.findAll() + grocy_tasks = self.find_all() self.merge_into_tw(taskwarrior_tasks, grocy_tasks) else: raise Exception('Could not determine resolution for grocy')