From 81fe923fb39a522a61933c906438efc37ffc9435 Mon Sep 17 00:00:00 2001 From: cs Date: Fri, 23 Feb 2024 20:42:16 +0000 Subject: [PATCH 01/10] Add some first unit tests --- .idea/ActiveCollabAPI.iml | 1 + .idea/runConfigurations/Unittests.xml | 20 +++++++ ActiveCollabAPI/ACAuthenticator.py | 21 ++++--- main.py | 46 +++++++++------ tests/__init__.py | 0 tests/test_ACAuthenticator.py | 85 +++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 .idea/runConfigurations/Unittests.xml create mode 100644 tests/__init__.py create mode 100644 tests/test_ACAuthenticator.py diff --git a/.idea/ActiveCollabAPI.iml b/.idea/ActiveCollabAPI.iml index 078a7e4..ef1836e 100644 --- a/.idea/ActiveCollabAPI.iml +++ b/.idea/ActiveCollabAPI.iml @@ -3,6 +3,7 @@ + diff --git a/.idea/runConfigurations/Unittests.xml b/.idea/runConfigurations/Unittests.xml new file mode 100644 index 0000000..ab865b6 --- /dev/null +++ b/.idea/runConfigurations/Unittests.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/ActiveCollabAPI/ACAuthenticator.py b/ActiveCollabAPI/ACAuthenticator.py index 0c96fb6..4c807e1 100644 --- a/ActiveCollabAPI/ACAuthenticator.py +++ b/ActiveCollabAPI/ACAuthenticator.py @@ -3,17 +3,20 @@ import json # 3rd party libs import requests +from requests import Response from ActiveCollabAPI import AC_LOGIN_BASE_URL, AC_USER_AGENT class ACAuthenticator: - - base_url = AC_LOGIN_BASE_URL + base_url = "" user = None accounts = None + def __init__(self, base_url: str): + self.base_url = base_url + def headers(self): return { 'Content-Type': 'application/json; charset=utf8', @@ -21,14 +24,18 @@ class ACAuthenticator: 'User-Agent': AC_USER_AGENT } - def login(self, email, password): + def login(self, email: str, password: str) -> Response: login_data = { 'email': email, 'password': password } - res = requests.post(self.base_url, - data=json.dumps(login_data), - headers=self.headers()) + return requests.post(self.base_url, + data=json.dumps(login_data), + headers=self.headers()) + + def login_with_check(self, email: str, password: str): + res = self.login(email, password) + if res.status_code != 200: raise Exception('Login failed!') @@ -38,7 +45,7 @@ class ACAuthenticator: self.user = login_res['user'] self.accounts = login_res['accounts'] - return login_res + return res def find_account(self, value, key='user_display_name'): found = list(filter(lambda a: a[key] == value, diff --git a/main.py b/main.py index f9e6fff..d7f882b 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ import configparser import logging from pprint import pprint +from ActiveCollabAPI import AC_LOGIN_BASE_URL from ActiveCollabAPI.ACAuthenticator import ACAuthenticator from ActiveCollabAPI.ACClient import ACClient from ActiveCollabAPI.ACTokenAuthenticator import ACTokenAuthenticator @@ -19,7 +20,8 @@ requests_log = logging.getLogger("urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True -if __name__ == "__main__": + +def main(): config = configparser.ConfigParser() config.read('config.ini') @@ -28,32 +30,37 @@ if __name__ == "__main__": account_name = config['LOGIN']['account_name'] # 1. you need to log in to get a session and a list of accounts - auth = ACAuthenticator() - session = auth.login(username, password) - pprint(session) - user = session['user'] + auth = ACAuthenticator(AC_LOGIN_BASE_URL) + auth.login_with_check(username, password) # use first account account = auth.find_account(account_name) # 2. you need to get a token for this account - # token = ACTokenAuthenticator(account, user).issue_token_intent() + token = ACTokenAuthenticator(account, auth.user).issue_token_intent() # pprint(token) # 3. now you can query the API - # ac = ACClient(account, token) + ac = ACClient(account, token) + + # check version + res = ac.get_info() + ac_version = res.json() + pprint(ac_version) + if ac_version['version'] != '7.4.337': + raise Exception("Supporting only Version 7.4.337!") # get a list of all projects # res = ac.get_projects() # pprint(res.json()) - # get task with sub tasks - # https://app.activecollab.com/416910/projects/310/tasks/6274 + # get task with sub-tasks + # "https://app.activecollab.com/416910/projects/310/tasks/6260" # project_id = 310 - # task_id = 6281 + # task_id = 6260 # task_res = ac.get_project_task(project_id, task_id) # pprint(task_res.json()) # task = task_res.json() - # res = ac.unassign_all_sub_task(task) + # ac.delete_all_open_subtasks(task) # create a new subtask for the task without assignment # subtask = { @@ -66,10 +73,10 @@ if __name__ == "__main__": # pprint(res.json()) # # bulk create subtasks - subtask_texts = [] - with open("sub-tasks.txt", "r", encoding="utf-8") as fh: - for line in fh: - subtask_texts.append(line.rstrip('\n')) + # subtask_texts = [] + # with open("sub-tasks.txt", "r", encoding="utf-8") as fh: + # for line in fh: + # subtask_texts.append(line.rstrip('\n')) # create # for text in subtask_texts: # subtask['body'] = text @@ -98,5 +105,10 @@ if __name__ == "__main__": # res = ac.delete_subtask(subtask) # pprint(res.json()) - # res = ac.get_info() - # pprint(res.json()) + res = ac.get_projects() + if res.status_code == 200: + pprint(res.json()) + + +if __name__ == "__main__": + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ACAuthenticator.py b/tests/test_ACAuthenticator.py new file mode 100644 index 0000000..0e6ff5b --- /dev/null +++ b/tests/test_ACAuthenticator.py @@ -0,0 +1,85 @@ +import json +from unittest import TestCase +from unittest.mock import patch + +from ActiveCollabAPI import AC_LOGIN_BASE_URL +from ActiveCollabAPI.ACAuthenticator import ACAuthenticator + + +class TestACAuthenticator(TestCase): + def test_headers(self): + authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) + headers = authenticator.headers() + self.assertIn('Content-Type', headers) + self.assertEqual('application/json; charset=utf8', headers['Content-Type']) + self.assertIn('Accept', headers) + self.assertEqual('application/json', headers['Accept']) + self.assertIn('User-Agent', headers) + + def mock_login_success(self): + return { + "accounts": [ + { + "name": 12345678, + "url": "https:\/\/app.activecollab.com\/12345678", + "display_name": "433339", + "user_display_name": "#12345678", + "position": 1, + "class": "ActiveCollab\\Shepherd\\Model\\Account\\ActiveCollab\\FeatherAccount", + "status": "trial" + } + ], + "is_ok": 1, + "user": { + "avatar_url": "https:\/\/activecollab.com\/avatar.php?user_id=12345678&size=80", + "first_name": "users first", + "last_name": "users last", + "intent": "random-string"} + } + + def mock_login_failed(self): + return { + "is_ok": 3 + } + + def test_login_success_gets_user_and_accounts_list(self): + with patch("ActiveCollabAPI.ACAuthenticator.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = self.mock_login_success() + + email = "collab@example.com" + password = "VeryS3cret!" + authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) + response = authenticator.login_with_check(email, password) + + self.assertEquals(200, response.status_code) + res_data = response.json() + self.assertEqual(1, res_data['is_ok']) + user = res_data["user"] + self.assertEqual("users first", user["first_name"]) + self.assertEqual("users last", user["last_name"]) + accounts = res_data["accounts"] + self.assertGreater(len(accounts), 0) + self.assertEqual(12345678, accounts[0]["name"]) + + def test_login_failed_http401(self): + with self.assertRaises(Exception) as exc: + with patch("ActiveCollabAPI.ACAuthenticator.requests.post") as mock_post: + mock_post.return_value.status_code = 401 + mock_post.return_value.json.return_value = self.mock_login_failed() + + email = "collab@example.com" + password = "VeryS3cret!" + authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) + response = authenticator.login_with_check(email, password) + + def test_login_failed_not_ok(self): + with self.assertRaises(Exception) as exc: + with patch("ActiveCollabAPI.ACAuthenticator.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = self.mock_login_failed() + + email = "collab@example.com" + password = "VeryS3cret!" + authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) + response = authenticator.login_with_check(email, password) From 662b75e224b5645f6e7a43a0dd86204cc6f3ef4d Mon Sep 17 00:00:00 2001 From: cs Date: Fri, 23 Feb 2024 21:08:10 +0000 Subject: [PATCH 02/10] Fixes --- tests/test_ACAuthenticator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_ACAuthenticator.py b/tests/test_ACAuthenticator.py index 0e6ff5b..98d9b3d 100644 --- a/tests/test_ACAuthenticator.py +++ b/tests/test_ACAuthenticator.py @@ -52,7 +52,7 @@ class TestACAuthenticator(TestCase): authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) response = authenticator.login_with_check(email, password) - self.assertEquals(200, response.status_code) + self.assertEqual(200, response.status_code) res_data = response.json() self.assertEqual(1, res_data['is_ok']) user = res_data["user"] @@ -71,7 +71,7 @@ class TestACAuthenticator(TestCase): email = "collab@example.com" password = "VeryS3cret!" authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) - response = authenticator.login_with_check(email, password) + authenticator.login_with_check(email, password) def test_login_failed_not_ok(self): with self.assertRaises(Exception) as exc: @@ -82,4 +82,4 @@ class TestACAuthenticator(TestCase): email = "collab@example.com" password = "VeryS3cret!" authenticator = ACAuthenticator(AC_LOGIN_BASE_URL) - response = authenticator.login_with_check(email, password) + authenticator.login_with_check(email, password) From 4be5d4c3259b1f7a9e4dae18dd3ef69ef4a9c557 Mon Sep 17 00:00:00 2001 From: cs Date: Sat, 24 Feb 2024 12:02:12 +0000 Subject: [PATCH 03/10] Reorg code and tests DON'T use main.py --- .gitignore | 1 + .idea/ActiveCollabAPI.iml | 1 + .idea/runConfigurations/Integrationtests.xml | 20 ++++++ .idea/runConfigurations/Unittests.xml | 2 +- ActiveCollabAPI/ACAuthenticator.py | 27 +------- ActiveCollabAPI/ACTokenAuthenticator.py | 38 +++++------- ActiveCollabAPI/AcAccount.py | 12 ++++ ActiveCollabAPI/AcSession.py | 13 ++++ ActiveCollabAPI/AcToken.py | 6 ++ ActiveCollabAPI/AcUser.py | 9 +++ ActiveCollabAPI/ActiveCollab.py | 62 +++++++++++++++++++ Dockerfile | 2 +- integration_tests/__init__.py | 2 + integration_tests/test_Integration_AcLogin.py | 22 +++++++ main.py | 10 +-- test-config.json | 8 +++ tests/test_ACAuthenticator.py | 62 ++++++++++--------- tests/test_ACTokenAuthenticator.py | 59 ++++++++++++++++++ 18 files changed, 271 insertions(+), 85 deletions(-) create mode 100644 .idea/runConfigurations/Integrationtests.xml create mode 100644 ActiveCollabAPI/AcAccount.py create mode 100644 ActiveCollabAPI/AcSession.py create mode 100644 ActiveCollabAPI/AcToken.py create mode 100644 ActiveCollabAPI/AcUser.py create mode 100644 ActiveCollabAPI/ActiveCollab.py create mode 100644 integration_tests/__init__.py create mode 100644 integration_tests/test_Integration_AcLogin.py create mode 100644 test-config.json create mode 100644 tests/test_ACTokenAuthenticator.py diff --git a/.gitignore b/.gitignore index d708872..28dbaf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ config.ini .venv/ __pycache__/ +config-mira.ini diff --git a/.idea/ActiveCollabAPI.iml b/.idea/ActiveCollabAPI.iml index ef1836e..b52f417 100644 --- a/.idea/ActiveCollabAPI.iml +++ b/.idea/ActiveCollabAPI.iml @@ -3,6 +3,7 @@ + diff --git a/.idea/runConfigurations/Integrationtests.xml b/.idea/runConfigurations/Integrationtests.xml new file mode 100644 index 0000000..675ce8b --- /dev/null +++ b/.idea/runConfigurations/Integrationtests.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Unittests.xml b/.idea/runConfigurations/Unittests.xml index ab865b6..0a85783 100644 --- a/.idea/runConfigurations/Unittests.xml +++ b/.idea/runConfigurations/Unittests.xml @@ -12,7 +12,7 @@