diff --git a/.github/workflows/selenium.yml b/.github/workflows/selenium.yml index fab0faab..1408a3d3 100644 --- a/.github/workflows/selenium.yml +++ b/.github/workflows/selenium.yml @@ -1,10 +1,8 @@ name: Selenium on: - push: - branches: [master, testing] pull_request: - branches: [master, testing] + types: [ready_for_review, review_requested] jobs: build: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8d1307be..4d3d3d8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,3 +25,7 @@ repos: entry: npm run babel language: node files: ^ereuse_devicehub/static/js/main_inventory.js + - repo: https://github.com/jazzband/pip-tools + rev: 6.8.0 + hooks: + - id: pip-compile diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 142fd954..90d9b598 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,3 +30,29 @@ pre-commit install Do this: `device_detail.html` Don't do this: `DeviceDetail.html`, `Device-detail.html` + + +## Adding a new dependency to the project +This project tracks its packages using pip-tools, it could be installed by running: +``` +pip install pip-tools +``` + +Whenever you need to install a new package using pip install : +1. Put the package name into `requirements.in` instead. +``` +# requirements.in +... +new_package +``` + +2. Compile the requirements +``` +pip-compile requirements.in --output-file=requirements.txt + +``` + +3. Then install upgraded dependencies: +``` +pip install -U -r requirements.txt +``` diff --git a/requirements.in b/requirements.in new file mode 100644 index 00000000..13862036 --- /dev/null +++ b/requirements.in @@ -0,0 +1,41 @@ +alembic==1.4.2 +atomicwrites==1.4.0 +click-spinner==0.1.8 +colorama==0.3.9 +colour==0.1.5 +ereuse-utils[naming,test,session,cli]==0.4.0b50 +Flask-Cors==3.0.10 +Flask-Login==0.5.0 +Flask-WTF==1.0.0 +flask-weasyprint==0.4 +hashids==1.2.0 +more-itertools==8.12.0 +passlib==1.7.1 +phonenumbers==8.9.11 +psycopg2-binary==2.8.3 +pyjwt==2.4.0 +python-decouple==3.3 +python-dotenv==0.14.0 +python-stdnum==1.9 +pyyaml==5.4 +requests==2.27.1 +requests-mock==1.5.2 +requests-toolbelt==0.9.1 +sortedcontainers==2.1.0 +sqlalchemy-citext==1.3.post0 +sqlalchemy-utils==0.33.11 +teal==0.2.0a38 +tqdm==4.32.2 + +# workbench json parsing dependencies +pint==0.9 +py-dmidecode==0.1.0 +pandas==1.3.5 +numpy==1.22.0 # pandas dependency +odfpy==1.4.1 # pandas dependency +xlrd==2.0.1 # pandas dependency +openpyxl==3.0.10 # pandas dependency +et_xmlfile==1.1.0 # pandas dependency + +# manual dependency +marshmallow-enum==1.4.1 diff --git a/requirements.txt b/requirements.txt index 8ec1e0a4..d698c4c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,53 +1,226 @@ +# +# This file is autogenerated by pip-compile with python 3.8 +# To update, run: +# +# pip-compile --output-file=requirements.txt requirements.in +# alembic==1.4.2 + # via -r requirements.in anytree==2.4.3 + # via teal apispec==0.39.0 + # via teal +atomicwrites==1.4.0 + # via -r requirements.in boltons==18.0.1 + # via + # ereuse-utils + # teal +cairocffi==1.4.0 + # via + # cairosvg + # weasyprint +cairosvg==2.5.2 + # via weasyprint +certifi==2022.9.24 + # via requests +cffi==1.15.1 + # via + # cairocffi + # weasyprint +charset-normalizer==2.0.12 + # via requests click==6.7 + # via + # ereuse-utils + # flask click-spinner==0.1.8 + # via + # -r requirements.in + # teal colorama==0.3.9 + # via + # -r requirements.in + # ereuse-utils colour==0.1.5 -ereuse-utils[naming,test,session,cli]==0.4.0b50 -Flask==1.0.2 -Flask-Cors==3.0.10 -Flask-Login==0.5.0 -Flask-SQLAlchemy==2.3.2 -Flask-WTF==1.0.0 + # via + # -r requirements.in + # sqlalchemy-utils +cssselect2==0.7.0 + # via + # cairosvg + # weasyprint +defusedxml==0.7.1 + # via + # cairosvg + # odfpy +ereuse-utils[cli,naming,session,test]==0.4.0b50 + # via + # -r requirements.in + # teal +et-xmlfile==1.1.0 + # via + # -r requirements.in + # openpyxl +flask==1.0.2 + # via + # ereuse-utils + # flask-cors + # flask-login + # flask-sqlalchemy + # flask-weasyprint + # flask-wtf + # teal +flask-cors==3.0.10 + # via + # -r requirements.in + # teal +flask-login==0.5.0 + # via -r requirements.in +flask-sqlalchemy==2.5.1 + # via teal +flask-weasyprint==0.4 + # via -r requirements.in +flask-wtf==1.0.0 + # via -r requirements.in hashids==1.2.0 + # via -r requirements.in +html5lib==1.1 + # via weasyprint +idna==3.4 + # via requests inflection==0.3.1 + # via ereuse-utils itsdangerous==2.0.1 -# lock Jinja2 version because it's the latest compatible with Flask 1.0.X -# see related info on https://github.com/pallets/jinja/issues/1628 -Jinja2==3.0.3 + # via + # flask + # flask-wtf +jinja2==3.0.3 + # via flask +mako==1.2.3 + # via alembic +markupsafe==2.1.1 + # via + # jinja2 + # mako + # wtforms marshmallow==3.0.0b11 + # via + # marshmallow-enum + # teal + # webargs marshmallow-enum==1.4.1 -passlib==1.7.1 -phonenumbers==8.9.11 -pytest==3.7.2 -pytest-runner==4.2 -python-dateutil==2.7.3 -python-stdnum==1.9 -PyYAML==5.4 -requests[security]==2.27.1 -requests-mock==1.5.2 -SQLAlchemy==1.3.24 -SQLAlchemy-Utils==0.33.11 -teal==0.2.0a38 -webargs==5.5.3 -Werkzeug==0.15.5 -sqlalchemy-citext==1.3.post0 -flask-weasyprint==0.5 -weasyprint==44 -psycopg2-binary==2.8.3 -sortedcontainers==2.1.0 -tqdm==4.32.2 -python-decouple==3.3 -python-dotenv==0.14.0 -pyjwt==2.4.0 -pint==0.9 -py-dmidecode==0.1.0 + # via -r requirements.in +more-itertools==8.12.0 + # via -r requirements.in +numpy==1.22.0 + # via + # -r requirements.in + # pandas +odfpy==1.4.1 + # via -r requirements.in +openpyxl==3.0.10 + # via -r requirements.in pandas==1.3.5 -numpy==1.22.0 # pandas dependency -odfpy==1.4.1 # pandas dependency -xlrd==2.0.1 # pandas dependency -openpyxl==3.0.10 # pandas dependency -et_xmlfile==1.1.0 # pandas dependency + # via -r requirements.in +passlib==1.7.1 + # via + # -r requirements.in + # sqlalchemy-utils +phonenumbers==8.9.11 + # via + # -r requirements.in + # sqlalchemy-utils +pillow==9.2.0 + # via cairosvg +pint==0.9 + # via -r requirements.in +psycopg2-binary==2.8.3 + # via -r requirements.in +py-dmidecode==0.1.0 + # via -r requirements.in +pycparser==2.21 + # via cffi +pyjwt==2.4.0 + # via -r requirements.in +pyphen==0.13.0 + # via weasyprint +python-dateutil==2.7.3 + # via + # alembic + # pandas +python-decouple==3.3 + # via -r requirements.in +python-dotenv==0.14.0 + # via -r requirements.in +python-editor==1.0.4 + # via alembic +python-stdnum==1.9 + # via -r requirements.in +pytz==2022.2.1 + # via pandas +pyyaml==5.4 + # via + # -r requirements.in + # apispec +requests==2.27.1 + # via + # -r requirements.in + # requests-mock + # requests-toolbelt +requests-mock==1.5.2 + # via -r requirements.in +requests-toolbelt==0.9.1 + # via + # -r requirements.in + # ereuse-utils +six==1.16.0 + # via + # anytree + # flask-cors + # html5lib + # python-dateutil + # requests-mock + # sqlalchemy-utils +sortedcontainers==2.1.0 + # via -r requirements.in +sqlalchemy==1.3.24 + # via + # alembic + # flask-sqlalchemy + # sqlalchemy-citext + # sqlalchemy-utils +sqlalchemy-citext==1.3.post0 + # via -r requirements.in +sqlalchemy-utils[color,password,phone]==0.33.11 + # via + # -r requirements.in + # teal +teal==0.2.0a38 + # via -r requirements.in +tinycss2==1.1.1 + # via + # cairosvg + # cssselect2 + # weasyprint +tqdm==4.32.2 + # via + # -r requirements.in + # ereuse-utils +urllib3==1.26.12 + # via requests +weasyprint==44 + # via flask-weasyprint +webargs==5.5.3 + # via teal +webencodings==0.5.1 + # via + # cssselect2 + # html5lib + # tinycss2 +werkzeug==2.0.3 + # via flask +wtforms==3.0.1 + # via flask-wtf +xlrd==2.0.1 + # via -r requirements.in diff --git a/tests/test_action.py b/tests/test_action.py index 14b9d697..40e9cd15 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -1,5 +1,4 @@ import copy -import ipaddress import json import os import shutil @@ -7,7 +6,7 @@ from datetime import datetime, timedelta from decimal import Decimal from io import BytesIO from json.decoder import JSONDecodeError -from typing import Tuple, Type +from typing import Tuple import pytest from dateutil.tz import tzutc @@ -15,7 +14,7 @@ from flask import current_app as app from flask import g from pytest import raises from sqlalchemy.util import OrderedSet -from teal.enums import Currency, Subdivision +from teal.enums import Currency from ereuse_devicehub.client import Client, UserClient from ereuse_devicehub.db import db @@ -82,11 +81,7 @@ def test_erase_basic(): def test_validate_device_data_storage(): """Checks the validation for data-storage-only actions works.""" # We can't set a GraphicCard - with pytest.raises( - TypeError, - message='EraseBasic.device must be a DataStorage ' - 'but you passed ', - ): + with pytest.raises(TypeError): models.EraseBasic( device=GraphicCard( serial_number='foo', manufacturer='bar', model='foo-bar' @@ -94,6 +89,10 @@ def test_validate_device_data_storage(): clean_with_zeros=True, **conftest.T, ) + pytest.fail( + 'EraseBasic.device must be a DataStorage ' + 'but you passed ' + ) @pytest.mark.mvp @@ -292,9 +291,7 @@ def test_generic_action( for ams in [models.Recycling, models.Use, models.Refurbish, models.Management] ), ) -def test_simple_status_actions( - action_model: models.Action, user2: UserClient -): +def test_simple_status_actions(action_model: models.Action, user2: UserClient): """Simple test of status action.""" user = user2 snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) @@ -554,7 +551,9 @@ def test_status_without_lot(action_model: models.Action, user: UserClient): for ams in [models.Recycling, models.Use, models.Refurbish, models.Management] ), ) -def test_status_in_temporary_lot(action_model: models.Action, user: UserClient, app: Devicehub): +def test_status_in_temporary_lot( + action_model: models.Action, user: UserClient, app: Devicehub +): """Test of status actions for devices in a temporary lot.""" snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) abstract = Device.query.filter_by(id=snap['device']['id']).first() @@ -727,7 +726,7 @@ def test_live_without_TestDataStorage(user: UserClient, client: Client, app: Dev acer = file('acer.happy.battery.snapshot') snapshot, _ = user.post(acer, res=models.Snapshot) device_id = snapshot['device']['id'] - db_device = Device.query.filter_by(id=device_id).one() + post_request = { "transaction": "ccc", "name": "John", @@ -767,7 +766,7 @@ def test_live_without_hdd_1(user: UserClient, client: Client, app: Devicehub): acer = file('acer.happy.battery.snapshot') snapshot, _ = user.post(acer, res=models.Snapshot) device_id = snapshot['device']['id'] - db_device = Device.query.filter_by(id=device_id).one() + post_request = { "transaction": "ccc", "name": "John", @@ -803,7 +802,7 @@ def test_live_without_hdd_2(user: UserClient, client: Client, app: Devicehub): acer['components'] = components snapshot, _ = user.post(json_encode(acer), res=models.Snapshot) device_id = snapshot['device']['id'] - db_device = Device.query.filter_by(id=device_id).one() + post_request = { "transaction": "ccc", "name": "John", @@ -838,7 +837,7 @@ def test_live_without_hdd_3(user: UserClient, client: Client, app: Devicehub): acer['components'] = components snapshot, _ = user.post(json_encode(acer), res=models.Snapshot) device_id = snapshot['device']['id'] - db_device = Device.query.filter_by(id=device_id).one() + post_request = { "transaction": "ccc", "name": "John", @@ -875,7 +874,7 @@ def test_live_with_hdd_with_old_time(user: UserClient, client: Client, app: Devi acer = file('acer.happy.battery.snapshot') snapshot, _ = user.post(acer, res=models.Snapshot) device_id = snapshot['device']['id'] - db_device = Device.query.filter_by(id=device_id).one() + post_request = { "transaction": "ccc", "name": "John", @@ -1022,7 +1021,7 @@ def test_allocate(user: UserClient): allocate, _ = user.post(res=models.Allocate, data=post_request) # Normal allocate device, _ = user.get(res=Device, item=devicehub_id) - assert device['allocated'] == True + assert device['allocated'] is True action = [a for a in device['actions'] if a['type'] == 'Allocate'][0] assert action['transaction'] == allocate['transaction'] assert action['finalUserCode'] == allocate['finalUserCode'] @@ -1097,11 +1096,11 @@ def test_deallocate(user: UserClient): user.post(res=models.Allocate, data=post_allocate) device, _ = user.get(res=Device, item=devicehub_id) - assert device['allocated'] == True + assert device['allocated'] is True deallocate, _ = user.post(res=models.Deallocate, data=post_deallocate) assert deallocate['startTime'] == post_deallocate['startTime'] assert deallocate['devices'][0]['id'] == device_id - assert deallocate['devices'][0]['allocated'] == False + assert deallocate['devices'][0]['allocated'] is False res, _ = user.post(res=models.Deallocate, data=post_deallocate, status=422) assert res['code'] == 422 assert res['type'] == 'ValidationError' @@ -1204,8 +1203,8 @@ def test_offer_without_to(user: UserClient): users = [ac.user for ac in trade.acceptances] assert trade.user_to == device.owner assert request_post['code'].lower() in device.owner.email - assert device.owner.active == False - assert device.owner.phantom == True + assert device.owner.active is False + assert device.owner.phantom is True assert trade.user_to in users assert trade.user_from in users assert device.owner.email != user.email @@ -1283,8 +1282,8 @@ def test_offer_without_from(user: UserClient, user2: UserClient): phantom_user = trade.user_from assert request_post['code'].lower() in phantom_user.email - assert phantom_user.active == False - assert phantom_user.phantom == True + assert phantom_user.active is False + assert phantom_user.phantom is True # assert trade.confirm_transfer users = [ac.user for ac in trade.acceptances] @@ -1778,12 +1777,12 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): assert device_10 in trade.devices assert len(trade.devices) == 10 - # the SCRAP confirms the revoke action - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-2].id, - 'devices': [snap10['device']['id']], - } + # TODO??? the SCRAP confirms the revoke action + # request_confirm_revoke = { + # 'type': 'ConfirmRevoke', + # 'action': device_10.actions[-2].id, + # 'devices': [snap10['device']['id']], + # } # check validation error # user2.post(res=models.Action, data=request_confirm_revoke, status=422) @@ -2796,7 +2795,7 @@ def test_action_web_erase(user: UserClient, client: Client): ) assert "alert alert-info" in response assert "100% coincidence." in response - assert not "alert alert-danger" in response + assert "alert alert-danger" not in response @pytest.mark.mvp @@ -2805,7 +2804,7 @@ def test_moveOnDocument(user: UserClient, user2: UserClient): lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot) lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot) url = ( - 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa', + 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaap', ) request_post1 = { 'filename': 'test.pdf', @@ -2887,7 +2886,7 @@ def test_delete_devices(user: UserClient): assert action_delete.t == 'Delete' assert str(action_delete.id) == action['id'] - assert db_device.active == False + assert db_device.active is False # Check use of filter from frontend url = '/devices/?filter={"type":["Computer"]}' @@ -2953,7 +2952,6 @@ def test_delete_devices_permitions(user: UserClient, user2: UserClient): file_snap = file('1-device-with-components.snapshot') snap, _ = user.post(file_snap, res=models.Snapshot) - device = Device.query.filter_by(id=snap['device']['id']).one() request = { 'type': 'Delete', @@ -2973,7 +2971,7 @@ def test_moveOnDocument_bug168(user: UserClient, user2: UserClient): lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot) lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot) url = ( - 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa', + 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaap', ) request_post1 = { 'filename': 'test.pdf', @@ -3014,13 +3012,12 @@ def test_moveOnDocument_bug168(user: UserClient, user2: UserClient): 'container_to': id_hash, 'description': description, } - doc, _ = user.post(res=models.Action, data=request_moveOn) - trade = models.Trade.query.one() + user.post(res=models.Action, data=request_moveOn) trade_document1 = TradeDocument.query.filter_by(id=tradedocument_from['id']).one() trade_document2 = TradeDocument.query.filter_by(id=tradedocument_to['id']).one() assert trade_document1.total_weight == 150.0 assert trade_document2.total_weight == 4.0 assert trade_document1.trading == 'Confirm' - assert trade_document2.trading == None + assert trade_document2.trading is None tradedocument, _ = user.delete(res=TradeDocument, item=tradedocument_to['id']) diff --git a/tests/test_auth.py b/tests/test_auth.py index 81cefb0e..54c70a3e 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -21,14 +21,22 @@ def test_authenticate_success(app: Devicehub): def test_authenticate_error(app: Devicehub): """Tests the authenticate method with wrong token values.""" with app.app_context(): - MESSAGE = 'Provide a suitable token.' create_user() # Token doesn't exist - with pytest.raises(Unauthorized, message=MESSAGE): + with pytest.raises(Unauthorized): app.auth.authenticate(token=str(uuid4())) + pytest.fail('Provide a suitable token.') + + +@pytest.mark.mvp +def test_authenticate_error_malformed_token(app: Devicehub): + """Tests the authenticate method with malformed token.""" + with app.app_context(): + create_user() # Wrong token format - with pytest.raises(Unauthorized, message=MESSAGE): + with pytest.raises(Unauthorized): app.auth.authenticate(token='this is a wrong uuid') + pytest.fail('Provide a suitable token.') @pytest.mark.mvp @@ -36,4 +44,6 @@ def test_auth_view(user: UserClient, client: Client): """Tests authentication at endpoint / view.""" user.get(res='User', item=user.user['id'], status=200) client.get(res='User', item=user.user['id'], status=Unauthorized) - client.get(res='User', item=user.user['id'], token='wrong token', status=Unauthorized) + client.get( + res='User', item=user.user['id'], token='wrong token', status=Unauthorized + ) diff --git a/tests/test_device.py b/tests/test_device.py index f422c653..ac8c1d07 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,13 +1,12 @@ import copy import datetime -import pytest - from uuid import UUID -from flask import g +import pytest from colour import Color from ereuse_utils.naming import Naming from ereuse_utils.test import ANY +from flask import g from pytest import raises from sqlalchemy.util import OrderedSet from teal.db import ResourceNotFound @@ -20,26 +19,35 @@ from ereuse_devicehub.resources.action import models as m from ereuse_devicehub.resources.action.models import Remove, TestConnectivity from ereuse_devicehub.resources.agent.models import Person from ereuse_devicehub.resources.device import models as d -from ereuse_devicehub.resources.device.exceptions import NeedsId from ereuse_devicehub.resources.device.schemas import Device as DeviceS -from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, MismatchBetweenTagsAndHid, \ - Sync -from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech, Severity, \ - SnapshotSoftware, TransferState +from ereuse_devicehub.resources.device.sync import ( + MismatchBetweenTags, + MismatchBetweenTagsAndHid, + Sync, +) +from ereuse_devicehub.resources.enums import ( + ComputerChassis, + DisplayTech, + Severity, + SnapshotSoftware, + TransferState, +) from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.user import User from tests import conftest -from tests.conftest import file, yaml2json, json_encode +from tests.conftest import file, json_encode, yaml2json @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_device_model(): """Tests that the correctness of the device model and its relationships.""" - pc = d.Desktop(model='p1mo', - manufacturer='p1ma', - serial_number='p1s', - chassis=ComputerChassis.Tower) + pc = d.Desktop( + model='p1mo', + manufacturer='p1ma', + serial_number='p1s', + chassis=ComputerChassis.Tower, + ) net = d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s') graphic = d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500) pc.components.add(net) @@ -55,7 +63,9 @@ def test_device_model(): # Removing a component from pc doesn't delete the component pc.components.remove(net) db.session.commit() - pc = d.Device.query.filter_by(id=pc.id).first() # this is the same as querying for d.Desktop directly + pc = d.Device.query.filter_by( + id=pc.id + ).first() # this is the same as querying for d.Desktop directly assert pc.components == {graphic} network_adapter = d.NetworkAdapter.query.one() assert network_adapter not in pc.components @@ -72,7 +82,9 @@ def test_device_model(): assert network_adapter.id == 4 assert d.NetworkAdapter.query.first() is not None, 'We removed the network adaptor' assert gcard.id == 5, 'We should still hold a reference to a zombie graphic card' - assert d.GraphicCard.query.first() is None, 'We should have deleted it –it was inside the pc' + assert ( + d.GraphicCard.query.first() is None + ), 'We should have deleted it –it was inside the pc' @pytest.mark.xfail(reason='Test not developed') @@ -92,21 +104,25 @@ def test_device_schema(): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_physical_properties(): - c = d.Motherboard(slots=2, - usb=3, - serial_number='sn', - model='ml', - manufacturer='mr', - width=2.0, - color=Color()) - pc = d.Desktop(chassis=ComputerChassis.Tower, - model='foo', - manufacturer='bar', - serial_number='foo-bar', - weight=2.8, - width=1.4, - height=2.1, - color=Color('LightSeaGreen')) + c = d.Motherboard( + slots=2, + usb=3, + serial_number='sn', + model='ml', + manufacturer='mr', + width=2.0, + color=Color(), + ) + pc = d.Desktop( + chassis=ComputerChassis.Tower, + model='foo', + manufacturer='bar', + serial_number='foo-bar', + weight=2.8, + width=1.4, + height=2.1, + color=Color('LightSeaGreen'), + ) pc.components.add(c) db.session.add(pc) db.session.commit() @@ -122,7 +138,7 @@ def test_physical_properties(): 'manufacturer': 'mr', 'bios_date': None, 'ram_max_size': None, - 'ram_slots': None + 'ram_slots': None, } assert pc.physical_properties == { 'chassis': ComputerChassis.Tower, @@ -132,7 +148,7 @@ def test_physical_properties(): 'receiver_id': None, 'serial_number': 'foo-bar', 'part_number': None, - 'transfer_state': TransferState.Initial + 'transfer_state': TransferState.Initial, } @@ -142,14 +158,19 @@ def test_component_similar_one(): user = User.query.filter().first() snapshot = yaml2json('pc-components.db') pc = snapshot['device'] - snapshot['components'][0]['serial_number'] = snapshot['components'][1]['serial_number'] = None - pc = d.Desktop(**pc, components=OrderedSet(d.Component(**c) for c in snapshot['components'])) + snapshot['components'][0]['serial_number'] = snapshot['components'][1][ + 'serial_number' + ] = None + pc = d.Desktop( + **pc, components=OrderedSet(d.Component(**c) for c in snapshot['components']) + ) component1, component2 = pc.components # type: d.Component db.session.add(pc) db.session.flush() # Let's create a new component named 'A' similar to 1 - componentA = d.Component(model=component1.model, manufacturer=component1.manufacturer, - owner_id=user.id) + componentA = d.Component( + model=component1.model, manufacturer=component1.manufacturer, owner_id=user.id + ) similar_to_a = componentA.similar_one(pc, set()) assert similar_to_a == component1 # d.Component B does not have the same model @@ -175,9 +196,11 @@ def test_add_remove(): pc = d.Desktop(**pc, components=OrderedSet([c1, c2])) db.session.add(pc) c3 = d.Component(serial_number='nc1', owner_id=user.id) - pc2 = d.Desktop(serial_number='s2', - components=OrderedSet([c3]), - chassis=ComputerChassis.Microtower) + pc2 = d.Desktop( + serial_number='s2', + components=OrderedSet([c3]), + chassis=ComputerChassis.Microtower, + ) c4 = d.Component(serial_number='c4s', owner_id=user.id) db.session.add(pc2) db.session.add(c4) @@ -201,7 +224,9 @@ def test_sync_run_components_empty(): remove all the components from the device. """ s = yaml2json('pc-components.db') - pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])) + pc = d.Desktop( + **s['device'], components=OrderedSet(d.Component(**c) for c in s['components']) + ) db.session.add(pc) db.session.commit() @@ -219,7 +244,9 @@ def test_sync_run_components_none(): keep all the components from the device. """ s = yaml2json('pc-components.db') - pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])) + pc = d.Desktop( + **s['device'], components=OrderedSet(d.Component(**c) for c in s['components']) + ) db.session.add(pc) db.session.commit() @@ -249,7 +276,8 @@ def test_sync_execute_register_desktop_existing_no_tag(): db.session.commit() pc = d.Desktop( - **yaml2json('pc-components.db')['device']) # Create a new transient non-db object + **yaml2json('pc-components.db')['device'] + ) # Create a new transient non-db object # 1: device exists on DB db_pc = Sync().execute_register(pc) pc.amount = 0 @@ -285,7 +313,9 @@ def test_sync_execute_register_desktop_tag_not_linked(): db.session.commit() # Create a new transient non-db object - pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')])) + pc = d.Desktop( + **yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')]) + ) returned_pc = Sync().execute_register(pc) assert returned_pc == pc assert tag.device == pc, 'Tag has to be linked' @@ -326,7 +356,9 @@ def test_sync_execute_register_tag_does_not_exist(): Tags have to be created before trying to link them through a Snapshot. """ user = User.query.filter().first() - pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')])) + pc = d.Desktop( + **yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')]) + ) pc.owner_id = user.id with raises(ResourceNotFound): Sync().execute_register(pc) @@ -345,7 +377,8 @@ def test_sync_execute_register_tag_linked_same_device(): db.session.commit() pc = d.Desktop( - **yaml2json('pc-components.db')['device']) # Create a new transient non-db object + **yaml2json('pc-components.db')['device'] + ) # Create a new transient non-db object pc.tags.add(Tag(id='foo')) db_pc = Sync().execute_register(pc) assert db_pc.id == orig_pc.id @@ -369,7 +402,8 @@ def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags(): db.session.commit() pc1 = d.Desktop( - **yaml2json('pc-components.db')['device']) # Create a new transient non-db object + **yaml2json('pc-components.db')['device'] + ) # Create a new transient non-db object pc1.tags.add(Tag(id='foo-1')) pc1.tags.add(Tag(id='foo-2')) with raises(MismatchBetweenTags): @@ -393,7 +427,8 @@ def test_sync_execute_register_mismatch_between_tags_and_hid(): db.session.commit() pc1 = d.Desktop( - **yaml2json('pc-components.db')['device']) # Create a new transient non-db object + **yaml2json('pc-components.db')['device'] + ) # Create a new transient non-db object pc1.tags.add(Tag(id='foo-2')) with raises(MismatchBetweenTagsAndHid): Sync().execute_register(pc1) @@ -404,22 +439,36 @@ def test_sync_execute_register_mismatch_between_tags_and_hid(): def test_get_device(user: UserClient): """Checks GETting a d.Desktop with its components.""" g.user = User.query.one() - pc = d.Desktop(model='p1mo', - manufacturer='p1ma', - serial_number='p1s', - chassis=ComputerChassis.Tower, - owner_id=user.user['id']) - pc.components = OrderedSet([ - d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s', - owner_id=user.user['id']), - d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']) - ]) + pc = d.Desktop( + model='p1mo', + manufacturer='p1ma', + serial_number='p1s', + chassis=ComputerChassis.Tower, + owner_id=user.user['id'], + ) + pc.components = OrderedSet( + [ + d.NetworkAdapter( + model='c1mo', + manufacturer='c1ma', + serial_number='c1s', + owner_id=user.user['id'], + ), + d.GraphicCard( + model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id'] + ), + ] + ) db.session.add(pc) # todo test is an abstract class. replace with another one - db.session.add(TestConnectivity(device=pc, - severity=Severity.Info, - agent=Person(name='Timmy'), - author=User(email='bar@bar.com'))) + db.session.add( + TestConnectivity( + device=pc, + severity=Severity.Info, + agent=Person(name='Timmy'), + author=User(email='bar@bar.com'), + ) + ) db.session.commit() pc_api, _ = user.get(res=d.Device, item=pc.devicehub_id) assert len(pc_api['actions']) == 1 @@ -427,10 +476,14 @@ def test_get_device(user: UserClient): assert pc_api['actions'][0]['device'] == pc.id assert pc_api['actions'][0]['severity'] == 'Info' assert UUID(pc_api['actions'][0]['author']) - assert 'actions_components' not in pc_api, 'actions_components are internal use only' + assert ( + 'actions_components' not in pc_api + ), 'actions_components are internal use only' assert 'actions_one' not in pc_api, 'they are internal use only' assert 'author' not in pc_api - assert tuple(c['id'] for c in pc_api['components']) == tuple(c.id for c in pc.components) + assert tuple(c['id'] for c in pc_api['components']) == tuple( + c.id for c in pc.components + ) assert pc_api['hid'] == 'desktop-p1ma-p1mo-p1s' assert pc_api['model'] == 'p1mo' assert pc_api['manufacturer'] == 'p1ma' @@ -443,41 +496,59 @@ def test_get_device(user: UserClient): def test_get_devices(app: Devicehub, user: UserClient): """Checks GETting multiple devices.""" g.user = User.query.one() - pc = d.Desktop(model='p1mo', - manufacturer='p1ma', - serial_number='p1s', - chassis=ComputerChassis.Tower, - owner_id=user.user['id']) - pc.components = OrderedSet([ - d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s', - owner_id=user.user['id']), - d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500, - owner_id=user.user['id']) - ]) - pc1 = d.Desktop(model='p2mo', - manufacturer='p2ma', - serial_number='p2s', - chassis=ComputerChassis.Tower, - owner_id=user.user['id']) - pc2 = d.Laptop(model='p3mo', - manufacturer='p3ma', - serial_number='p3s', - chassis=ComputerChassis.Netbook, - owner_id=user.user['id']) + pc = d.Desktop( + model='p1mo', + manufacturer='p1ma', + serial_number='p1s', + chassis=ComputerChassis.Tower, + owner_id=user.user['id'], + ) + pc.components = OrderedSet( + [ + d.NetworkAdapter( + model='c1mo', + manufacturer='c1ma', + serial_number='c1s', + owner_id=user.user['id'], + ), + d.GraphicCard( + model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id'] + ), + ] + ) + pc1 = d.Desktop( + model='p2mo', + manufacturer='p2ma', + serial_number='p2s', + chassis=ComputerChassis.Tower, + owner_id=user.user['id'], + ) + pc2 = d.Laptop( + model='p3mo', + manufacturer='p3ma', + serial_number='p3s', + chassis=ComputerChassis.Netbook, + owner_id=user.user['id'], + ) db.session.add_all((pc, pc1, pc2)) db.session.commit() devices, _ = user.get(res=d.Device) ids = (pc.id, pc1.id, pc2.id, pc.components[0].id, pc.components[1].id) assert tuple(dev['id'] for dev in devices['items']) == ids assert tuple(dev['type'] for dev in devices['items']) == ( - d.Desktop.t, d.Desktop.t, d.Laptop.t, d.NetworkAdapter.t, d.GraphicCard.t + d.Desktop.t, + d.Desktop.t, + d.Laptop.t, + d.NetworkAdapter.t, + d.GraphicCard.t, ) @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_get_device_permissions(app: Devicehub, user: UserClient, user2: UserClient, - client: Client): +def test_get_device_permissions( + app: Devicehub, user: UserClient, user2: UserClient, client: Client +): """Checks GETting a d.Desktop with its components.""" s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) @@ -529,12 +600,12 @@ def test_get_devices_unassigned(user: UserClient): assert len(devices['items']) == 2 from ereuse_devicehub.resources.lot.models import Lot + device_id = devices['items'][0]['id'] my_lot, _ = user.post(({'name': 'My_lot'}), res=Lot) - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(my_lot['id']), - query=[('id', device_id)]) + lot, _ = user.post( + {}, res=Lot, item='{}/devices'.format(my_lot['id']), query=[('id', device_id)] + ) lot = Lot.query.filter_by(id=lot['id']).one() assert next(iter(lot.devices)).id == device_id @@ -554,13 +625,15 @@ def test_get_devices_unassigned(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_computer_monitor(): - m = d.ComputerMonitor(technology=DisplayTech.LCD, - manufacturer='foo', - model='bar', - serial_number='foo-bar', - resolution_width=1920, - resolution_height=1080, - size=14.5) + m = d.ComputerMonitor( + technology=DisplayTech.LCD, + manufacturer='foo', + model='bar', + serial_number='foo-bar', + resolution_width=1920, + resolution_height=1080, + size=14.5, + ) db.session.add(m) db.session.commit() @@ -568,9 +641,11 @@ def test_computer_monitor(): @pytest.mark.mvp def test_manufacturer(user: UserClient): m, r = user.get(res='Manufacturer', query=[('search', 'asus')]) - assert m == {'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]} + assert m == { + 'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}] + } assert r.cache_control.public - assert r.expires > datetime.datetime.now() + assert r.expires.timestamp() > datetime.datetime.now().timestamp() @pytest.mark.mvp @@ -591,12 +666,20 @@ def test_device_properties_format(app: Devicehub, user: UserClient): assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116' assert pc.ram_size == 1024 assert pc.data_storage_size == 152627 - assert pc.graphic_card_model == 'mobile 945gse express integrated graphics controller' + assert ( + pc.graphic_card_model + == 'mobile 945gse express integrated graphics controller' + ) assert pc.processor_model == 'intel atom cpu n270 @ 1.60ghz' net = next(c for c in pc.components if isinstance(c, d.NetworkAdapter)) - assert format(net) == 'NetworkAdapter 5: model ar8121/ar8113/ar8114 ' \ - 'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d' - assert format(net, 't') == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet' + assert ( + format(net) == 'NetworkAdapter 5: model ar8121/ar8113/ar8114 ' + 'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d' + ) + assert ( + format(net, 't') + == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet' + ) assert format(net, 's') == 'qualcomm atheros 00:24:8C:7F:CF:2D – 100 Mbps' hdd = next(c for c in pc.components if isinstance(c, d.DataStorage)) assert format(hdd) == 'HardDrive 10: model st9160310as, S/N 5sv4tqa6' @@ -638,8 +721,12 @@ def test_networking_model(user: UserClient): @pytest.mark.usefixtures(conftest.app_context.__name__) def test_cooking_mixer(user: UserClient): - mixer = d.Mixer(serial_number='foo', model='bar', manufacturer='foobar', - owner_id=user.user['id']) + mixer = d.Mixer( + serial_number='foo', + model='bar', + manufacturer='foobar', + owner_id=user.user['id'], + ) db.session.add(mixer) db.session.commit() @@ -652,12 +739,12 @@ def test_cooking_mixer_api(user: UserClient): 'serialNumber': 'foo', 'model': 'bar', 'manufacturer': 'foobar', - 'type': 'Mixer' + 'type': 'Mixer', }, 'version': '11.0', - 'software': SnapshotSoftware.Web.name + 'software': SnapshotSoftware.Web.name, }, - res=m.Snapshot + res=m.Snapshot, ) mixer, _ = user.get(res=d.Device, item=snapshot['device']['id']) assert mixer['type'] == 'Mixer' @@ -673,14 +760,19 @@ def test_hid_with_mac(app: Devicehub, user: UserClient): pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() - assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + assert ( + pc.placeholder.binding.hid + == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + ) @pytest.mark.mvp def test_hid_without_mac(app: Devicehub, user: UserClient): """Checks hid without mac.""" snapshot = yaml2json('asus-eee-1000h.snapshot.11') - snapshot['components'] = [c for c in snapshot['components'] if c['type'] != 'NetworkAdapter'] + snapshot['components'] = [ + c for c in snapshot['components'] if c['type'] != 'NetworkAdapter' + ] snap, _ = user.post(json_encode(snapshot), res=m.Snapshot) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' @@ -709,7 +801,10 @@ def test_hid_with_2networkadapters(app: Devicehub, user: UserClient): devices, _ = user.get(res=d.Device) laptop = devices['items'][0] - assert laptop['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + assert ( + laptop['hid'] + == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + ) assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2 @@ -726,14 +821,20 @@ def test_hid_with_2network_and_drop_no_mac_in_hid(app: Devicehub, user: UserClie pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() - assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + assert ( + pc.placeholder.binding.hid + == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + ) snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb' snapshot['components'] = [c for c in snapshot['components'] if c != network] user.post(json_encode(snapshot), res=m.Snapshot) devices, _ = user.get(res=d.Device) laptop = devices['items'][0] - assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + assert ( + pc.placeholder.binding.hid + == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + ) assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2 assert len([c for c in laptop['components'] if c['type'] == 'NetworkAdapter']) == 1 @@ -752,7 +853,10 @@ def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() - assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + assert ( + pc.placeholder.binding.hid + == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' + ) # we drop the network card then is used for to build the hid snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb' @@ -762,19 +866,25 @@ def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient) laptops = [c for c in devices['items'] if c['type'] == 'Laptop'] assert len(laptops) == 4 hids = [laptops[0]['hid'], laptops[2]['hid']] - proof_hid = ['laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', - 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'] + proof_hid = [ + 'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', + 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d', + ] assert all([h in proof_hid for h in hids]) # we drop all network cards snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abc' - snapshot['components'] = [c for c in snapshot['components'] if c not in [network, network2]] + snapshot['components'] = [ + c for c in snapshot['components'] if c not in [network, network2] + ] user.post(json_encode(snapshot), res=m.Snapshot) devices, _ = user.get(res=d.Device) laptops = [c for c in devices['items'] if c['type'] == 'Laptop'] assert len(laptops) == 4 hids = [laptops[0]['hid'], laptops[2]['hid']] - proof_hid = ['laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', - 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d', - 'laptop-asustek_computer_inc-1000h-94oaaq021116'] + proof_hid = [ + 'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', + 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d', + 'laptop-asustek_computer_inc-1000h-94oaaq021116', + ] assert all([h in proof_hid for h in hids]) diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 70e4dfb7..8d939733 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -21,10 +21,12 @@ from tests.conftest import TestConfig class NoExcCliRunner(click.testing.CliRunner): """Runner that interfaces with the Devicehub CLI.""" - def invoke(self, *args, input=None, env=None, catch_exceptions=False, color=False, - **extra): - r = super().invoke(ereuse_devicehub.cli.cli, - args, input, env, catch_exceptions, color, **extra) + def invoke( + self, *args, input=None, env=None, catch_exceptions=False, color=False, **extra + ): + r = super().invoke( + ereuse_devicehub.cli.cli, args, input, env, catch_exceptions, color, **extra + ) assert r.exit_code == 0, 'CLI code {}: {}'.format(r.exit_code, r.output) return r @@ -69,16 +71,26 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2): """ # Create first DB cli.inv('tdb1') - cli.invoke('inv', 'add', - '-n', 'Test DB1', - '-on', 'ACME DB1', - '-oi', 'acme-id', - '-tu', 'https://example.com', - '-tt', '3c66a6ad-22de-4db6-ac46-d8982522ec40', - '--common') + cli.invoke( + 'inv', + 'add', + '-n', + 'Test DB1', + '-on', + 'ACME DB1', + '-oi', + 'acme-id', + '-tu', + 'https://example.com', + '-tt', + '3c66a6ad-22de-4db6-ac46-d8982522ec40', + '--common', + ) # Create an user for first DB - cli.invoke('user', 'add', 'foo@foo.com', '-a', 'Foo', '-c', 'ES', '-p', 'Such password') + cli.invoke( + 'user', 'add', 'foo@foo.com', '-a', 'Foo', '-c', 'ES', '-p', 'Such password' + ) with tdb1.app_context(): # There is a row for the inventory @@ -98,12 +110,20 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2): cli.inv('tdb2') # Create a second DB # Note how we don't create common anymore - cli.invoke('inv', 'add', - '-n', 'Test DB2', - '-on', 'ACME DB2', - '-oi', 'acme-id-2', - '-tu', 'https://example.com', - '-tt', 'fbad1c08-ffdc-4a61-be49-464962c186a8') + cli.invoke( + 'inv', + 'add', + '-n', + 'Test DB2', + '-on', + 'ACME DB2', + '-oi', + 'acme-id-2', + '-tu', + 'https://example.com', + '-tt', + 'fbad1c08-ffdc-4a61-be49-464962c186a8', + ) # Create an user for with access for both DB cli.invoke('user', 'add', 'bar@bar.com', '-a', 'Bar', '-p', 'Wow password') @@ -144,5 +164,6 @@ def test_create_existing_inventory(cli, tdb1): cli.invoke('inv', 'add', '--common') with tdb1.app_context(): assert db.has_schema('tdb1') - with pytest.raises(AssertionError, message='Schema tdb1 already exists.'): + with pytest.raises(AssertionError): cli.invoke('inv', 'add', '--common') + pytest.fail('Schema tdb1 already exists.')