From d988e094f7ee26f3769a561c7d493fde02dd9567 Mon Sep 17 00:00:00 2001 From: Muflone Date: Sun, 6 Jun 2021 22:30:04 +0200 Subject: [PATCH] Added nose tests --- .circleci/config.yml | 1 + .gitignore | 4 + .travis.yml | 3 + requirements_dev.txt | 4 +- requirements_travis.txt | 2 + tests/test_contacts.py | 288 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 tests/test_contacts.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 97e2716..174615f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,3 +31,4 @@ jobs: python -m flake8 pyodoo samples python setup.py install --optimize=1 --root=build ls -laR build + nosetests --verbose --detailed-errors --nocapture --with-id --with-coverage tests/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9f31700..7ce554b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ __pycache__/ # Ignore build and dist folders for PyPI /build/ /dist/ + +# Ignore tests and coverage +/.coverage +/.noseids \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 34330a2..8c1e958 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,6 @@ script: - python -m flake8 . - python setup.py install --optimize=1 --root=build - ls -laR build + - nosetests --verbose --detailed-errors --nocapture --with-id --with-coverage tests/ +after_success: + - coveralls \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index 5a15cdb..42d52ab 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,4 +2,6 @@ flake8==3.9.2 build==0.4.0 -twine==3.4.1 \ No newline at end of file +twine==3.4.1 +nose==1.3.7 +coveralls==3.1.0 \ No newline at end of file diff --git a/requirements_travis.txt b/requirements_travis.txt index 76208e5..b106d3b 100644 --- a/requirements_travis.txt +++ b/requirements_travis.txt @@ -2,3 +2,5 @@ flake8==3.9.2 pycodestyle==2.7.0 +nose==1.3.7 +coveralls==3.1.0 \ No newline at end of file diff --git a/tests/test_contacts.py b/tests/test_contacts.py new file mode 100644 index 0000000..c9241da --- /dev/null +++ b/tests/test_contacts.py @@ -0,0 +1,288 @@ +## +# Project: PyOdoo +# Description: API for Odoo +# Author: Fabio Castelli (Muflone) +# Copyright: 2021 Fabio Castelli +# License: GPL-3+ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +## + +import unittest +import xmlrpc.client + +from pyodoo import (ActiveStatusChoice, + BooleanOperator, + CompareType, + Filter) +from pyodoo.constants import APP_NAME, APP_VERSION +from pyodoo.v12 import Model + + +class TestCaseContacts(unittest.TestCase): + @classmethod + def setUpClass(cls): + """ + Model object preparation + """ + # Get the free public server credentials + info = xmlrpc.client.ServerProxy('https://demo.odoo.com/start').start() + cls.model = Model(model_name='res.partner', + endpoint=info['host'], + database=info['database'], + username=info['user'], + password=info['password'], + language='en_US') + + def test_01_authenticate(self) -> None: + """ + Check the authentication + """ + results = self.model.authenticate() + # Check if the user ID is not None + self.assertIsNotNone(results) + # Check if the user ID is > 0 + self.assertGreater(results, 0) + + def test_02_search_all(self) -> None: + """ + Search all the rows in the model + """ + results = self.model.search(filters=[]) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + + def test_03_search_with_filters(self) -> None: + """ + Search some rows using filters + """ + # Filters by name and excluding an explicit ID + filters = [BooleanOperator.AND, + Filter(field='active', + compare_type=CompareType.EQUAL, + value=False), + BooleanOperator.NOT, + Filter(field='name', + compare_type=CompareType.CONTAINS, + value='OdooBot'), + ] + results = self.model.search(filters=filters) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + # Check if the results list does not contain the OdooBot user ID (2) + self.assertNotIn(2, results) + + def test_04_find_by_id_single(self) -> None: + """ + Find a single row using its ID + """ + results = self.model.find(entity_ids=[3], + fields=('id', 'name', 'type', 'street')) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + # Check if the results list contains only a single item + self.assertEqual(len(results), 1) + # Check some data + self.assertEqual(results[0]['id'], 3) + self.assertEqual(results[0]['name'], 'Mitchell Admin') + self.assertEqual(results[0]['type'], 'contact') + self.assertGreater(len(results[0]['street']), 0) + + def test_05_find_by_id_active(self) -> None: + """ + Find a single active row using its ID + """ + results = self.model.find(entity_ids=[2, 11], + fields=('id', 'name', 'type', 'street'), + is_active=ActiveStatusChoice.ACTIVE) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + # Check if the results list contains only a single item + self.assertEqual(len(results), 1) + # Check some data + self.assertEqual(results[0]['id'], 11) + self.assertNotEqual(results[0]['name'], 'OdooBot') + self.assertEqual(results[0]['type'], 'contact') + self.assertGreater(len(results[0]['street']), 0) + + def test_06_find_by_id_inactive(self) -> None: + """ + Find a single inactive row using its ID + """ + results = self.model.find(entity_ids=[2, 11], + fields=('id', 'name', 'type', 'street'), + is_active=ActiveStatusChoice.INACTIVE) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + # Check if the results list contains only a single item + self.assertEqual(len(results), 1) + # Check some data + self.assertEqual(results[0]['id'], 2) + self.assertEqual(results[0]['name'], 'OdooBot') + self.assertEqual(results[0]['type'], 'contact') + self.assertGreater(len(results[0]['street']), 0) + + def test_07_find_by_id_both_active(self) -> None: + """ + Find two rows using their IDs, both active and inactive + """ + results = self.model.find(entity_ids=[2, 11], + fields=('id', 'name', 'type', 'street'), + is_active=ActiveStatusChoice.BOTH) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + # Check if the results list contains only a single item + self.assertEqual(len(results), 2) + # Check some data + for item in results: + self.assertIn(item['id'], (2, 11)) + self.assertEqual(item['type'], 'contact') + self.assertGreater(len(item['street']), 0) + + def test_08_find_by_id_multiple(self) -> None: + """ + Find multiple row using their IDs + """ + results = self.model.find(entity_ids=[3, 15], + fields=('id', 'name', 'type', 'street')) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + # Check if the results list does not contain more than 2 rows + self.assertLessEqual(len(results), 2) + # Check if the results list contains only two items + self.assertEqual(len(results), 2) + # Check some data + self.assertIn(results[0]['id'], (3, 15)) + + def test_09_filter(self) -> None: + """ + Find multiple rows using some filters + """ + # Filters by name and excluding an explicit ID + filters = [BooleanOperator.AND, + Filter(field='active', + compare_type=CompareType.EQUAL, + value=False), + BooleanOperator.NOT, + Filter(field='name', + compare_type=CompareType.CONTAINS, + value='OdooBot'), + ] + results = self.model.filter(filters=filters, + fields=('id', 'name', 'type', 'street')) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results list is not empty + self.assertGreater(len(results), 0) + + def test_10_get(self) -> None: + """ + Get a single row using ID + """ + results = self.model.get(entity_id=3, + fields=('id', 'name', 'type', 'street')) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results type is None or dict + self.assertIn(type(results), (None, dict)) + # Check if the results is not empty + self.assertGreater(len(results), 0) + # Check if the results contains 4 values, for the 4 fields + self.assertEqual(len(results), 4) + # Check some data + self.assertEqual(results['id'], 3) + self.assertEqual(results['name'], 'Mitchell Admin') + self.assertEqual(results['type'], 'contact') + self.assertGreater(len(results['street']), 0) + + def test_11_create(self) -> None: + """ + Create a new row + """ + values = {'name': f'{APP_NAME} v.{APP_VERSION}'} + results = self.model.create(values) + # Check if the results are not None + self.assertIsNotNone(results) + # Check if the results is not empty + self.assertGreater(results, 0) + + def test_12_update(self) -> None: + """ + Update the newly created rows + """ + filters = [Filter(field='name', + compare_type=CompareType.EQUAL, + value=f'{APP_NAME} v.{APP_VERSION}')] + results = self.model.search(filters=filters) + # Check if we have results + self.assertIsNotNone(results) + self.assertGreater(len(results), 0) + # Update found data + for entity_id in results: + self.model.update(entity_id=entity_id, + values={'street': 'TEST TEST TEST'}) + results_updated = self.model.get(entity_id=entity_id, + fields=('id', 'street')) + # Check if the results are not None + self.assertIsNotNone(results_updated) + # Check if the results contain two fields + self.assertEqual(len(results_updated), 2) + # Check the field id + self.assertEqual(results_updated['id'], entity_id) + # Check the field street + self.assertEqual(results_updated['street'], 'TEST TEST TEST') + + def test_13_delete(self) -> None: + """ + Delete the newly created rows. + This test may be skipped in the case there's an active PoS session + as Odoo doesn't allow contacts deletion when there's some active + PoS session + """ + filters = [Filter(field='name', + compare_type=CompareType.EQUAL, + value=f'{APP_NAME} v.{APP_VERSION}')] + results = self.model.search(filters=filters) + # Check if we have results + self.assertIsNotNone(results) + self.assertGreater(len(results), 0) + # Delete found data + for entity_id in results: + try: + self.model.delete(entity_id=entity_id) + results_updated = self.model.get(entity_id=entity_id, + fields=('id', 'street')) + # Check if the results are None, then deleted + self.assertIsNone(results_updated) + except xmlrpc.client.Fault as error: + if error.faultCode == 2: + # We have an active PoS session running + # It's not possible to delete contacts until it's closed + pass + else: + # We catched a different error, re-raise it + raise error