From f36fa2bb7ddc7b4c38459204caac3f443e133767 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 12 May 2021 11:47:03 +0200 Subject: [PATCH 01/31] add var in config --- ereuse_devicehub/config.py | 3 +++ tests/conftest.py | 1 + 2 files changed, 4 insertions(+) diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py index beef8b25..02d97da3 100644 --- a/ereuse_devicehub/config.py +++ b/ereuse_devicehub/config.py @@ -68,3 +68,6 @@ class DevicehubConfig(Config): """Admin email""" EMAIL_ADMIN = config('EMAIL_ADMIN', '') + + """Definition of path where save the documents of customers""" + PATH_DOCUMENTS_STORAGE = config('PATH_DOCUMENTS_STORAGE', '/tmp/') diff --git a/tests/conftest.py b/tests/conftest.py index d0a0bdd2..94abed59 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,7 @@ class TestConfig(DevicehubConfig): TMP_SNAPSHOTS = '/tmp/snapshots' TMP_LIVES = '/tmp/lives' EMAIL_ADMIN = 'foo@foo.com' + PATH_DOCUMENTS_STORAGE = '/tmp/trade_documents' @pytest.fixture(scope='session') From 90f3a097a0bf77d8d7b333670dc87f7ad79bf040 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 12 May 2021 11:47:40 +0200 Subject: [PATCH 02/31] split trade tests in an other file --- tests/test_action.py | 636 ---------------------------------------- tests/test_trade.py | 678 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 678 insertions(+), 636 deletions(-) create mode 100644 tests/test_trade.py diff --git a/tests/test_action.py b/tests/test_action.py index 346022d0..40b1c886 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -765,227 +765,6 @@ def test_trade_endpoint(user: UserClient, user2: UserClient): device2, _ = user2.get(res=Device, item=device['id']) assert device2['id'] == device['id'] - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_to(user: UserClient): - """Test one offer with automatic confirmation and without user to""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - device = Device.query.filter_by(id=snapshot['device']['id']).one() - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=[('id', device.id)]) - - # check the owner of the device - assert device.owner.email == user.email - for c in device.components: - assert c.owner.email == user.email - - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'userFrom': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': False, - 'code': 'MAX' - } - user.post(res=models.Action, data=request_post) - - trade = models.Trade.query.one() - assert device in trade.devices - # assert trade.confirm_transfer - 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 trade.user_to in users - assert trade.user_from in users - assert device.owner.email != user.email - for c in device.components: - assert c.owner.email != user.email - - # check if the user_from is owner of the devices - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'userFrom': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': False, - 'code': 'MAX' - } - user.post(res=models.Action, data=request_post, status=422) - trade = models.Trade.query.one() - - # Check if the new phantom account is reused and not duplicated - computer = file('1-device-with-components.snapshot') - snapshot2, _ = user.post(computer, res=models.Snapshot) - device2 = Device.query.filter_by(id=snapshot2['device']['id']).one() - lot2 = Lot('MyLot2') - lot2.owner_id = user.user['id'] - lot2.devices.add(device2) - db.session.add(lot2) - db.session.flush() - request_post2 = { - 'type': 'Trade', - 'devices': [device2.id], - 'userFrom': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot2.id, - 'confirm': False, - 'code': 'MAX' - } - user.post(res=models.Action, data=request_post2) - assert User.query.filter_by(email=device.owner.email).count() == 1 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_from(user: UserClient, user2: UserClient): - """Test one offer without confirmation and without user from""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - lot = Lot('MyLot') - lot.owner_id = user.user['id'] - device = Device.query.filter_by(id=snapshot['device']['id']).one() - - # check the owner of the device - assert device.owner.email == user.email - assert device.owner.email != user2.email - - lot.devices.add(device) - db.session.add(lot) - db.session.flush() - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot.id, - 'confirm': False, - 'code': 'MAX' - } - action, _ = user2.post(res=models.Action, data=request_post, status=422) - - request_post['userTo'] = user.email - action, _ = user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - 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 trade.confirm_transfer - - users = [ac.user for ac in trade.acceptances] - assert trade.user_to in users - assert trade.user_from in users - assert user.email in trade.devices[0].owner.email - assert device.owner.email != user2.email - assert device.owner.email == user.email - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_users(user: UserClient): - """Test one offer with doble confirmation""" - user2 = User(email='baz@baz.cxm', password='baz') - user2.individuals.add(Person(name='Tommy')) - db.session.add(user2) - db.session.commit() - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - lot = Lot('MyLot') - lot.owner_id = user.user['id'] - device = Device.query.filter_by(id=snapshot['device']['id']).one() - lot.devices.add(device) - db.session.add(lot) - db.session.flush() - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot.id, - 'confirm': False, - 'code': 'MAX' - } - action, response = user.post(res=models.Action, data=request_post, status=422) - txt = 'you need one user from or user to for to do a offer' - assert txt in action['message']['_schema'] - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer(user: UserClient): - """Test one offer with doble confirmation""" - user2 = User(email='baz@baz.cxm', password='baz') - user2.individuals.add(Person(name='Tommy')) - db.session.add(user2) - db.session.commit() - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - lot = Lot('MyLot') - lot.owner_id = user.user['id'] - device = Device.query.filter_by(id=snapshot['device']['id']).one() - assert device.owner.email == user.email - assert device.owner.email != user2.email - lot.devices.add(device) - db.session.add(lot) - db.session.flush() - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot.id, - 'confirm': True, - } - - action, _ = user.post(res=models.Action, data=request_post) - # no there are transfer of devices - assert device.owner.email == user.email - assert device.owner.email != user2.email - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_devices(user: UserClient): - """Test one offer with doble confirmation""" - user2 = User(email='baz@baz.cxm', password='baz') - user2.individuals.add(Person(name='Tommy')) - db.session.add(user2) - db.session.commit() - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - # no there are transfer of devices - - @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_price_custom(): @@ -1036,418 +815,3 @@ def test_erase_physical(): db.session.add(erasure) db.session.commit() - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_endpoint_confirm(user: UserClient, user2: UserClient): - """Check the normal creation and visualization of one confirmation trade""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - device_id = snapshot['device']['id'] - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=[('id', device_id)]) - - request_post = { - 'type': 'Trade', - 'devices': [device_id], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - assert trade.devices[0].owner.email == user.email - - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [device_id] - } - - user2.post(res=models.Action, data=request_confirm) - user2.post(res=models.Action, data=request_confirm, status=422) - assert len(trade.acceptances) == 2 - assert trade.devices[0].owner.email == user2.email - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_confirm_revoke(user: UserClient, user2: UserClient): - """Check the normal revoke of one confirmation""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - device_id = snapshot['device']['id'] - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=[('id', device_id)]) - - request_post = { - 'type': 'Trade', - 'devices': [device_id], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [device_id] - } - - request_revoke = { - 'type': 'Revoke', - 'action': trade.id, - 'devices': [device_id], - } - - - # Normal confirmation - user2.post(res=models.Action, data=request_confirm) - - # Normal revoke - user2.post(res=models.Action, data=request_revoke) - - # Error for try duplicate revoke - user2.post(res=models.Action, data=request_revoke, status=422) - assert len(trade.acceptances) == 3 - - # You can not to do one confirmation next of one revoke - user2.post(res=models.Action, data=request_confirm, status=422) - assert len(trade.acceptances) == 3 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_usecase_confirmation(user: UserClient, user2: UserClient): - """Example of one usecase about confirmation""" - # the pRp (manatest_usecase_confirmationger) creates a temporary lot - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - # The manager add 7 device into the lot - snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - snap2, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) - snap3, _ = user.post(file('asus-1001pxd.snapshot'), res=models.Snapshot) - snap4, _ = user.post(file('desktop-9644w8n-lenovo-0169622.snapshot'), res=models.Snapshot) - snap5, _ = user.post(file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot'), res=models.Snapshot) - snap6, _ = user.post(file('1-device-with-components.snapshot'), res=models.Snapshot) - snap7, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=models.Snapshot) - snap8, _ = user.post(file('complete.export.snapshot'), res=models.Snapshot) - snap9, _ = user.post(file('real-hp-quad-core.snapshot.11'), res=models.Snapshot) - snap10, _ = user.post(file('david.lshw.snapshot'), res=models.Snapshot) - - devices = [('id', snap1['device']['id']), - ('id', snap2['device']['id']), - ('id', snap3['device']['id']), - ('id', snap4['device']['id']), - ('id', snap5['device']['id']), - ('id', snap6['device']['id']), - ('id', snap7['device']['id']), - ('id', snap8['device']['id']), - ('id', snap9['device']['id']), - ('id', snap10['device']['id']), - ] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[:7]) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - # the SCRAP confirms 3 of the 10 devices in its outgoing lot - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [snap1['device']['id'], snap2['device']['id'], snap3['device']['id']] - } - assert trade.devices[0].actions[-2].t == 'Trade' - assert trade.devices[0].actions[-1].t == 'Confirm' - assert trade.devices[0].actions[-1].user == trade.user_to - - user2.post(res=models.Action, data=request_confirm) - assert trade.devices[0].actions[-1].t == 'Confirm' - assert trade.devices[0].actions[-1].user == trade.user_from - n_actions = len(trade.devices[0].actions) - - # check validation error - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ] - } - - user2.post(res=models.Action, data=request_confirm, status=422) - - - # The manager add 3 device more into the lot - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[7:]) - - assert trade.devices[-1].actions[-2].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_to - assert len(trade.devices[0].actions) == n_actions - - - # the SCRAP confirms the rest of devices - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap1['device']['id'], - snap2['device']['id'], - snap3['device']['id'], - snap4['device']['id'], - snap5['device']['id'], - snap6['device']['id'], - snap7['device']['id'], - snap8['device']['id'], - snap9['device']['id'], - snap10['device']['id'] - ] - } - - user2.post(res=models.Action, data=request_confirm) - assert trade.devices[-1].actions[-3].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_from - assert len(trade.devices[0].actions) == n_actions - - # The manager remove one device of the lot and automaticaly - # is create one revoke action - device_10 = trade.devices[-1] - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - assert len(trade.lot.devices) == len(trade.devices) == 9 - assert not device_10 in trade.devices - assert device_10.actions[-1].t == 'Revoke' - - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - - assert device_10.actions[-1].t == 'Revoke' - assert device_10.actions[-2].t == 'Confirm' - - # the SCRAP confirms the revoke action - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, - 'devices': [ - snap10['device']['id'] - ] - } - - user2.post(res=models.Action, data=request_confirm_revoke) - assert device_10.actions[-1].t == 'ConfirmRevoke' - assert device_10.actions[-2].t == 'Revoke' - - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, - 'devices': [ - snap9['device']['id'] - ] - } - - # check validation error - user2.post(res=models.Action, data=request_confirm_revoke, status=422) - - - # The manager add again device_10 - assert len(trade.devices) == 9 - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:]) - - assert device_10.actions[-1].t == 'Confirm' - assert device_10 in trade.devices - assert len(trade.devices) == 10 - - - # the SCRAP confirms the action trade for device_10 - request_reconfirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ] - } - # import pdb; pdb.set_trace() - user2.post(res=models.Action, data=request_reconfirm) - assert device_10.actions[-1].t == 'Confirm' - assert device_10.actions[-1].user == trade.user_from - assert device_10.actions[-2].t == 'Confirm' - assert device_10.actions[-2].user == trade.user_to - assert device_10.actions[-3].t == 'ConfirmRevoke' - assert len(device_10.actions) == 13 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_confirmRevoke(user: UserClient, user2: UserClient): - """Example of one usecase about confirmation""" - # the pRp (manatest_usecase_confirmationger) creates a temporary lot - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - # The manager add 7 device into the lot - snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - snap2, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) - snap3, _ = user.post(file('asus-1001pxd.snapshot'), res=models.Snapshot) - snap4, _ = user.post(file('desktop-9644w8n-lenovo-0169622.snapshot'), res=models.Snapshot) - snap5, _ = user.post(file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot'), res=models.Snapshot) - snap6, _ = user.post(file('1-device-with-components.snapshot'), res=models.Snapshot) - snap7, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=models.Snapshot) - snap8, _ = user.post(file('complete.export.snapshot'), res=models.Snapshot) - snap9, _ = user.post(file('real-hp-quad-core.snapshot.11'), res=models.Snapshot) - snap10, _ = user.post(file('david.lshw.snapshot'), res=models.Snapshot) - - devices = [('id', snap1['device']['id']), - ('id', snap2['device']['id']), - ('id', snap3['device']['id']), - ('id', snap4['device']['id']), - ('id', snap5['device']['id']), - ('id', snap6['device']['id']), - ('id', snap7['device']['id']), - ('id', snap8['device']['id']), - ('id', snap9['device']['id']), - ('id', snap10['device']['id']), - ] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - # the SCRAP confirms all of devices - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap1['device']['id'], - snap2['device']['id'], - snap3['device']['id'], - snap4['device']['id'], - snap5['device']['id'], - snap6['device']['id'], - snap7['device']['id'], - snap8['device']['id'], - snap9['device']['id'], - snap10['device']['id'] - ] - } - - user2.post(res=models.Action, data=request_confirm) - assert trade.devices[-1].actions[-3].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_from - - # The manager remove one device of the lot and automaticaly - # is create one revoke action - device_10 = trade.devices[-1] - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - assert len(trade.lot.devices) == len(trade.devices) == 9 - assert not device_10 in trade.devices - assert device_10.actions[-1].t == 'Revoke' - - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - - assert device_10.actions[-1].t == 'Revoke' - assert device_10.actions[-2].t == 'Confirm' - - # The manager add again device_10 - assert len(trade.devices) == 9 - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:]) - - assert device_10.actions[-1].t == 'Confirm' - 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'] - ] - } - - # check validation error - user2.post(res=models.Action, data=request_confirm_revoke, status=422) - - # the SCRAP confirms the action trade for device_10 - request_reconfirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ] - } - user2.post(res=models.Action, data=request_reconfirm) - assert device_10.actions[-1].t == 'Confirm' - assert device_10.actions[-1].user == trade.user_from - assert device_10.actions[-2].t == 'Confirm' - assert device_10.actions[-2].user == trade.user_to - assert device_10.actions[-3].t == 'Revoke' diff --git a/tests/test_trade.py b/tests/test_trade.py new file mode 100644 index 00000000..98d98ac7 --- /dev/null +++ b/tests/test_trade.py @@ -0,0 +1,678 @@ +import os +import ipaddress +import json +import shutil +import copy +import pytest + +from datetime import datetime, timedelta +from dateutil.tz import tzutc +from decimal import Decimal +from typing import Tuple, Type +from pytest import raises +from json.decoder import JSONDecodeError + +from flask import current_app as app, g +from sqlalchemy.util import OrderedSet +from teal.enums import Currency, Subdivision + +from ereuse_devicehub.db import db +from ereuse_devicehub.client import UserClient, Client +from ereuse_devicehub.devicehub import Devicehub +from ereuse_devicehub.resources import enums +from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.agent.models import Person +from ereuse_devicehub.resources.lot.models import Lot +from ereuse_devicehub.resources.action import models +from ereuse_devicehub.resources.device import states +from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \ + RamModule, SolidStateDrive +from ereuse_devicehub.resources.enums import ComputerChassis, Severity, TestDataStorageLength +from tests import conftest +from tests.conftest import create_user, file + + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_offer_without_to(user: UserClient): + """Test one offer with automatic confirmation and without user to""" + snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device = Device.query.filter_by(id=snapshot['device']['id']).one() + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device.id)]) + + # check the owner of the device + assert device.owner.email == user.email + for c in device.components: + assert c.owner.email == user.email + + request_post = { + 'type': 'Trade', + 'devices': [device.id], + 'userFrom': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': False, + 'code': 'MAX' + } + user.post(res=models.Action, data=request_post) + + trade = models.Trade.query.one() + assert device in trade.devices + # assert trade.confirm_transfer + 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 trade.user_to in users + assert trade.user_from in users + assert device.owner.email != user.email + for c in device.components: + assert c.owner.email != user.email + + # check if the user_from is owner of the devices + request_post = { + 'type': 'Trade', + 'devices': [device.id], + 'userFrom': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': False, + 'code': 'MAX' + } + user.post(res=models.Action, data=request_post, status=422) + trade = models.Trade.query.one() + + # Check if the new phantom account is reused and not duplicated + computer = file('1-device-with-components.snapshot') + snapshot2, _ = user.post(computer, res=models.Snapshot) + device2 = Device.query.filter_by(id=snapshot2['device']['id']).one() + lot2 = Lot('MyLot2') + lot2.owner_id = user.user['id'] + lot2.devices.add(device2) + db.session.add(lot2) + db.session.flush() + request_post2 = { + 'type': 'Trade', + 'devices': [device2.id], + 'userFrom': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot2.id, + 'confirm': False, + 'code': 'MAX' + } + user.post(res=models.Action, data=request_post2) + assert User.query.filter_by(email=device.owner.email).count() == 1 + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_offer_without_from(user: UserClient, user2: UserClient): + """Test one offer without confirmation and without user from""" + snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + lot = Lot('MyLot') + lot.owner_id = user.user['id'] + device = Device.query.filter_by(id=snapshot['device']['id']).one() + + # check the owner of the device + assert device.owner.email == user.email + assert device.owner.email != user2.email + + lot.devices.add(device) + db.session.add(lot) + db.session.flush() + request_post = { + 'type': 'Trade', + 'devices': [device.id], + 'userTo': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot.id, + 'confirm': False, + 'code': 'MAX' + } + action, _ = user2.post(res=models.Action, data=request_post, status=422) + + request_post['userTo'] = user.email + action, _ = user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + 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 trade.confirm_transfer + + users = [ac.user for ac in trade.acceptances] + assert trade.user_to in users + assert trade.user_from in users + assert user.email in trade.devices[0].owner.email + assert device.owner.email != user2.email + assert device.owner.email == user.email + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_offer_without_users(user: UserClient): + """Test one offer with doble confirmation""" + user2 = User(email='baz@baz.cxm', password='baz') + user2.individuals.add(Person(name='Tommy')) + db.session.add(user2) + db.session.commit() + snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + lot = Lot('MyLot') + lot.owner_id = user.user['id'] + device = Device.query.filter_by(id=snapshot['device']['id']).one() + lot.devices.add(device) + db.session.add(lot) + db.session.flush() + request_post = { + 'type': 'Trade', + 'devices': [device.id], + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot.id, + 'confirm': False, + 'code': 'MAX' + } + action, response = user.post(res=models.Action, data=request_post, status=422) + txt = 'you need one user from or user to for to do a offer' + assert txt in action['message']['_schema'] + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_offer(user: UserClient): + """Test one offer with doble confirmation""" + user2 = User(email='baz@baz.cxm', password='baz') + user2.individuals.add(Person(name='Tommy')) + db.session.add(user2) + db.session.commit() + snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + lot = Lot('MyLot') + lot.owner_id = user.user['id'] + device = Device.query.filter_by(id=snapshot['device']['id']).one() + assert device.owner.email == user.email + assert device.owner.email != user2.email + lot.devices.add(device) + db.session.add(lot) + db.session.flush() + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user.email, + 'userTo': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot.id, + 'confirm': True, + } + + action, _ = user.post(res=models.Action, data=request_post) + # no there are transfer of devices + assert device.owner.email == user.email + assert device.owner.email != user2.email + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_offer_without_devices(user: UserClient): + """Test one offer with doble confirmation""" + user2 = User(email='baz@baz.cxm', password='baz') + user2.individuals.add(Person(name='Tommy')) + db.session.add(user2) + db.session.commit() + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user.email, + 'userTo': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + # no there are transfer of devices + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_endpoint_confirm(user: UserClient, user2: UserClient): + """Check the normal creation and visualization of one confirmation trade""" + snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device_id = snapshot['device']['id'] + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device_id)]) + + request_post = { + 'type': 'Trade', + 'devices': [device_id], + 'userFrom': user.email, + 'userTo': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + assert trade.devices[0].owner.email == user.email + + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [device_id] + } + + user2.post(res=models.Action, data=request_confirm) + user2.post(res=models.Action, data=request_confirm, status=422) + assert len(trade.acceptances) == 2 + assert trade.devices[0].owner.email == user2.email + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_confirm_revoke(user: UserClient, user2: UserClient): + """Check the normal revoke of one confirmation""" + snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device_id = snapshot['device']['id'] + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device_id)]) + + request_post = { + 'type': 'Trade', + 'devices': [device_id], + 'userFrom': user.email, + 'userTo': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [device_id] + } + + request_revoke = { + 'type': 'Revoke', + 'action': trade.id, + 'devices': [device_id], + } + + + # Normal confirmation + user2.post(res=models.Action, data=request_confirm) + + # Normal revoke + user2.post(res=models.Action, data=request_revoke) + + # Error for try duplicate revoke + user2.post(res=models.Action, data=request_revoke, status=422) + assert len(trade.acceptances) == 3 + + # You can not to do one confirmation next of one revoke + user2.post(res=models.Action, data=request_confirm, status=422) + assert len(trade.acceptances) == 3 + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_usecase_confirmation(user: UserClient, user2: UserClient): + """Example of one usecase about confirmation""" + # the pRp (manatest_usecase_confirmationger) creates a temporary lot + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + # The manager add 7 device into the lot + snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + snap2, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) + snap3, _ = user.post(file('asus-1001pxd.snapshot'), res=models.Snapshot) + snap4, _ = user.post(file('desktop-9644w8n-lenovo-0169622.snapshot'), res=models.Snapshot) + snap5, _ = user.post(file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot'), res=models.Snapshot) + snap6, _ = user.post(file('1-device-with-components.snapshot'), res=models.Snapshot) + snap7, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=models.Snapshot) + snap8, _ = user.post(file('complete.export.snapshot'), res=models.Snapshot) + snap9, _ = user.post(file('real-hp-quad-core.snapshot.11'), res=models.Snapshot) + snap10, _ = user.post(file('david.lshw.snapshot'), res=models.Snapshot) + + devices = [('id', snap1['device']['id']), + ('id', snap2['device']['id']), + ('id', snap3['device']['id']), + ('id', snap4['device']['id']), + ('id', snap5['device']['id']), + ('id', snap6['device']['id']), + ('id', snap7['device']['id']), + ('id', snap8['device']['id']), + ('id', snap9['device']['id']), + ('id', snap10['device']['id']), + ] + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[:7]) + + # the manager shares the temporary lot with the SCRAP as an incoming lot + # for the CRAP to confirm it + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user2.email, + 'userTo': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + # the SCRAP confirms 3 of the 10 devices in its outgoing lot + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [snap1['device']['id'], snap2['device']['id'], snap3['device']['id']] + } + assert trade.devices[0].actions[-2].t == 'Trade' + assert trade.devices[0].actions[-1].t == 'Confirm' + assert trade.devices[0].actions[-1].user == trade.user_to + + user2.post(res=models.Action, data=request_confirm) + assert trade.devices[0].actions[-1].t == 'Confirm' + assert trade.devices[0].actions[-1].user == trade.user_from + n_actions = len(trade.devices[0].actions) + + # check validation error + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap10['device']['id'] + ] + } + + user2.post(res=models.Action, data=request_confirm, status=422) + + + # The manager add 3 device more into the lot + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[7:]) + + assert trade.devices[-1].actions[-2].t == 'Trade' + assert trade.devices[-1].actions[-1].t == 'Confirm' + assert trade.devices[-1].actions[-1].user == trade.user_to + assert len(trade.devices[0].actions) == n_actions + + + # the SCRAP confirms the rest of devices + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap1['device']['id'], + snap2['device']['id'], + snap3['device']['id'], + snap4['device']['id'], + snap5['device']['id'], + snap6['device']['id'], + snap7['device']['id'], + snap8['device']['id'], + snap9['device']['id'], + snap10['device']['id'] + ] + } + + user2.post(res=models.Action, data=request_confirm) + assert trade.devices[-1].actions[-3].t == 'Trade' + assert trade.devices[-1].actions[-1].t == 'Confirm' + assert trade.devices[-1].actions[-1].user == trade.user_from + assert len(trade.devices[0].actions) == n_actions + + # The manager remove one device of the lot and automaticaly + # is create one revoke action + device_10 = trade.devices[-1] + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) + assert len(trade.lot.devices) == len(trade.devices) == 9 + assert not device_10 in trade.devices + assert device_10.actions[-1].t == 'Revoke' + + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) + + assert device_10.actions[-1].t == 'Revoke' + assert device_10.actions[-2].t == 'Confirm' + + # the SCRAP confirms the revoke action + request_confirm_revoke = { + 'type': 'ConfirmRevoke', + 'action': device_10.actions[-1].id, + 'devices': [ + snap10['device']['id'] + ] + } + + user2.post(res=models.Action, data=request_confirm_revoke) + assert device_10.actions[-1].t == 'ConfirmRevoke' + assert device_10.actions[-2].t == 'Revoke' + + request_confirm_revoke = { + 'type': 'ConfirmRevoke', + 'action': device_10.actions[-1].id, + 'devices': [ + snap9['device']['id'] + ] + } + + # check validation error + user2.post(res=models.Action, data=request_confirm_revoke, status=422) + + + # The manager add again device_10 + assert len(trade.devices) == 9 + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:]) + + assert device_10.actions[-1].t == 'Confirm' + assert device_10 in trade.devices + assert len(trade.devices) == 10 + + + # the SCRAP confirms the action trade for device_10 + request_reconfirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap10['device']['id'] + ] + } + # import pdb; pdb.set_trace() + user2.post(res=models.Action, data=request_reconfirm) + assert device_10.actions[-1].t == 'Confirm' + assert device_10.actions[-1].user == trade.user_from + assert device_10.actions[-2].t == 'Confirm' + assert device_10.actions[-2].user == trade.user_to + assert device_10.actions[-3].t == 'ConfirmRevoke' + assert len(device_10.actions) == 13 + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_confirmRevoke(user: UserClient, user2: UserClient): + """Example of one usecase about confirmation""" + # the pRp (manatest_usecase_confirmationger) creates a temporary lot + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + # The manager add 7 device into the lot + snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + snap2, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) + snap3, _ = user.post(file('asus-1001pxd.snapshot'), res=models.Snapshot) + snap4, _ = user.post(file('desktop-9644w8n-lenovo-0169622.snapshot'), res=models.Snapshot) + snap5, _ = user.post(file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot'), res=models.Snapshot) + snap6, _ = user.post(file('1-device-with-components.snapshot'), res=models.Snapshot) + snap7, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=models.Snapshot) + snap8, _ = user.post(file('complete.export.snapshot'), res=models.Snapshot) + snap9, _ = user.post(file('real-hp-quad-core.snapshot.11'), res=models.Snapshot) + snap10, _ = user.post(file('david.lshw.snapshot'), res=models.Snapshot) + + devices = [('id', snap1['device']['id']), + ('id', snap2['device']['id']), + ('id', snap3['device']['id']), + ('id', snap4['device']['id']), + ('id', snap5['device']['id']), + ('id', snap6['device']['id']), + ('id', snap7['device']['id']), + ('id', snap8['device']['id']), + ('id', snap9['device']['id']), + ('id', snap10['device']['id']), + ] + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices) + + # the manager shares the temporary lot with the SCRAP as an incoming lot + # for the CRAP to confirm it + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user2.email, + 'userTo': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'documentID': '1', + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + # the SCRAP confirms all of devices + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap1['device']['id'], + snap2['device']['id'], + snap3['device']['id'], + snap4['device']['id'], + snap5['device']['id'], + snap6['device']['id'], + snap7['device']['id'], + snap8['device']['id'], + snap9['device']['id'], + snap10['device']['id'] + ] + } + + user2.post(res=models.Action, data=request_confirm) + assert trade.devices[-1].actions[-3].t == 'Trade' + assert trade.devices[-1].actions[-1].t == 'Confirm' + assert trade.devices[-1].actions[-1].user == trade.user_from + + # The manager remove one device of the lot and automaticaly + # is create one revoke action + device_10 = trade.devices[-1] + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) + assert len(trade.lot.devices) == len(trade.devices) == 9 + assert not device_10 in trade.devices + assert device_10.actions[-1].t == 'Revoke' + + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) + + assert device_10.actions[-1].t == 'Revoke' + assert device_10.actions[-2].t == 'Confirm' + + # The manager add again device_10 + assert len(trade.devices) == 9 + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:]) + + assert device_10.actions[-1].t == 'Confirm' + 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'] + ] + } + + # check validation error + user2.post(res=models.Action, data=request_confirm_revoke, status=422) + + # the SCRAP confirms the action trade for device_10 + request_reconfirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap10['device']['id'] + ] + } + user2.post(res=models.Action, data=request_reconfirm) + assert device_10.actions[-1].t == 'Confirm' + assert device_10.actions[-1].user == trade.user_from + assert device_10.actions[-2].t == 'Confirm' + assert device_10.actions[-2].user == trade.user_to + assert device_10.actions[-3].t == 'Revoke' + + + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_add_documentto_lot(user: UserClient, user2: UserClient): + """Example of one document inserted into one lot""" + lot, _ = user.post({'name': 'MyLot'}, res=Lot) From 5222f2ceb77d1ac1ab5f5157b6a380abfced426f Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 12 May 2021 11:48:52 +0200 Subject: [PATCH 03/31] add actionDocuments and drop documents field from Trade action --- ereuse_devicehub/resources/action/models.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index b7989585..26ed13d4 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -48,6 +48,7 @@ from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, Bio TestDataStorageLength from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.tradedocument.models import Document class JoinedTableMixin: @@ -295,6 +296,20 @@ class ActionDevice(db.Model): primary_key=True) +class ActionWithMultipleDocuments(Action): + documents = relationship(Document, + backref=backref('actions_multiple', lazy=True, **_sorted_actions), + secondary=lambda: ActionDocument.__table__, + order_by=lambda: Document.id, + collection_class=OrderedSet) + + +class ActionDocument(db.Model): + document_id = Column(BigInteger, ForeignKey(Document.id), primary_key=True) + action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDocuments.id), + primary_key=True) + + class Add(ActionWithOneDevice): """The act of adding components to a device. @@ -1473,7 +1488,7 @@ class ConfirmRevoke(Confirm): return '<{0.t} {0.id} accepted by {0.user}>'.format(self) -class Trade(JoinedTableMixin, ActionWithMultipleDevices): +class Trade(JoinedTableMixin, ActionWithMultipleDevices, ActionWithMultipleDocuments): """Trade actions log the political exchange of devices between users. Every time a trade action is performed, the old user looses its political possession, for example ownership, in favor of another @@ -1501,8 +1516,6 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices): currency = Column(DBEnum(Currency), nullable=False, default=Currency.EUR.name) currency.comment = """The currency of this price as for ISO 4217.""" date = Column(db.TIMESTAMP(timezone=True)) - document_id = Column(CIText()) - document_id.comment = """The id of one document like invoice so they can be linked.""" confirm = Column(Boolean, default=False, nullable=False) confirm.comment = """If you need confirmation of the user, you need actevate this field""" code = Column(CIText(), nullable=True) From c21de78982ef2cafcf49325c2c385e0b00b81c73 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 13 May 2021 13:35:46 +0200 Subject: [PATCH 04/31] base for documents --- ereuse_devicehub/resources/action/models.py | 9 +- ereuse_devicehub/resources/action/schemas.py | 1 - .../resources/tradedocument/__init__.py | 10 ++ .../resources/tradedocument/models.py | 115 ++++++++++++++++++ .../resources/tradedocument/schemas.py | 14 +++ .../resources/tradedocument/views.py | 35 ++++++ tests/test_trade.py | 11 -- 7 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 ereuse_devicehub/resources/tradedocument/__init__.py create mode 100644 ereuse_devicehub/resources/tradedocument/models.py create mode 100644 ereuse_devicehub/resources/tradedocument/schemas.py create mode 100644 ereuse_devicehub/resources/tradedocument/views.py diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 26ed13d4..fe23d2e2 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -296,9 +296,10 @@ class ActionDevice(db.Model): primary_key=True) -class ActionWithMultipleDocuments(Action): +class ActionWithMultipleDocuments(ActionWithMultipleDevices): + # pass documents = relationship(Document, - backref=backref('actions_multiple', lazy=True, **_sorted_actions), + backref=backref('actions_multiple_docs', lazy=True, **_sorted_actions), secondary=lambda: ActionDocument.__table__, order_by=lambda: Document.id, collection_class=OrderedSet) @@ -1448,7 +1449,7 @@ class CancelReservation(Organize): """The act of cancelling a reservation.""" -class Confirm(JoinedTableMixin, ActionWithMultipleDevices): +class Confirm(JoinedTableMixin, ActionWithMultipleDocuments): """Users confirm the one action trade this confirmation it's link to trade and the devices that confirm """ @@ -1488,7 +1489,7 @@ class ConfirmRevoke(Confirm): return '<{0.t} {0.id} accepted by {0.user}>'.format(self) -class Trade(JoinedTableMixin, ActionWithMultipleDevices, ActionWithMultipleDocuments): +class Trade(JoinedTableMixin, ActionWithMultipleDocuments): """Trade actions log the political exchange of devices between users. Every time a trade action is performed, the old user looses its political possession, for example ownership, in favor of another diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 9808d3ce..a8c7df85 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -499,7 +499,6 @@ class ConfirmRevoke(ActionWithMultipleDevices): class Trade(ActionWithMultipleDevices): __doc__ = m.Trade.__doc__ - document_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='documentID', required=False) date = DateTime(data_key='date', required=False) price = Float(required=False, data_key='price') user_to_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='userTo', missing='', diff --git a/ereuse_devicehub/resources/tradedocument/__init__.py b/ereuse_devicehub/resources/tradedocument/__init__.py new file mode 100644 index 00000000..0e75f6e8 --- /dev/null +++ b/ereuse_devicehub/resources/tradedocument/__init__.py @@ -0,0 +1,10 @@ +from teal.resource import Converters, Resource + +from ereuse_devicehub.resources.tradedocument import schemas +from ereuse_devicehub.resources.tradedocument.views import DocumentView + +class TradeDocumentDef(Resource): + SCHEMA = schemas.Document + VIEW = DocumentView + AUTH = True + ID_CONVERTER = Converters.string diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py new file mode 100644 index 00000000..85db108b --- /dev/null +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -0,0 +1,115 @@ +import os + +from itertools import chain +from citext import CIText +from flask import current_app as app, g + +from sqlalchemy.dialects.postgresql import UUID +from ereuse_devicehub.db import db +from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time + +from sqlalchemy import BigInteger, Boolean, Column, Float, ForeignKey, Integer, \ + Sequence, SmallInteger, Unicode, inspect, text +from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.orm import ColumnProperty, backref, relationship, validates +from sqlalchemy.util import OrderedSet +from sqlalchemy_utils import ColorType +from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, \ + check_lower, check_range +from teal.resource import url_for_resource + +from ereuse_devicehub.resources.utils import hashcode +from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, ComputerChassis, \ + DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState + + +class Document(Thing): + """This represent a document involved in a trade action. + Every document is added to a lot. + When this lot is converted in one trade, the action trade is added to the document + and the action trade need to be confirmed for the both users of the trade. + This confirmation can be revoked and this revoked need to be ConfirmRevoke for have + some efect. + + This documents can be invoices or list of devices or certificates of erasure of + one disk. + + Like a Devices one document have actions and is possible add or delete of one lot + if this lot don't have a trade + + The document is saved in the database + + """ + + id = Column(BigInteger, Sequence('device_seq'), primary_key=True) + id.comment = """The identifier of the device for this database. Used only + internally for software; users should not use this. + """ + # type = Column(Unicode(STR_SM_SIZE), nullable=False) + date = Column(db.DateTime) + date.comment = """The date of document, some documents need to have one date + """ + id_document = Column(CIText()) + id_document.comment = """The id of one document like invoice so they can be linked.""" + description = Column(db.CIText()) + description.comment = """A description of document.""" + owner_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), + nullable=False, + default=lambda: g.user.id) + owner = db.relationship(User, primaryjoin=owner_id == User.id) + file_name = Column(db.CIText()) + file_name.comment = """This is the name of the file when user up the document.""" + file_name_disk = Column(db.CIText()) + file_name_disk.comment = """This is the name of the file as devicehub save in server.""" + + __table_args__ = ( + db.Index('document_id', id, postgresql_using='hash'), + # db.Index('type_doc', type, postgresql_using='hash') + ) + + @property + def actions(self) -> list: + """All the actions where the device participated, including: + + 1. Actions performed directly to the device. + 2. Actions performed to a component. + 3. Actions performed to a parent device. + + Actions are returned by descending ``created`` time. + """ + return sorted(self.actions_multiple_docs, key=lambda x: x.created) + + @property + def path_to_file(self) -> str: + """The path of one file is defined by the owner, file_name and created time. + + """ + base = app.config['PATH_DOCUMENTS_STORAGE'] + file_name = "{0.date}-{0.filename}".format(self) + base = os.path.join(base, g.user.email, file_name) + return sorted(self.actions_multiple_docs, key=lambda x: x.created) + + def last_action_of(self, *types): + """Gets the last action of the given types. + + :raise LookupError: Device has not an action of the given type. + """ + try: + # noinspection PyTypeHints + actions = self.actions + actions.sort(key=lambda x: x.created) + return next(e for e in reversed(actions) if isinstance(e, types)) + except StopIteration: + raise LookupError('{!r} does not contain actions of types {}.'.format(self, types)) + + def _warning_actions(self, actions): + return sorted(ev for ev in actions if ev.severity >= Severity.Warning) + + + def __lt__(self, other): + return self.id < other.id + + def __str__(self) -> str: + return '{0.file_name}'.format(self) diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py new file mode 100644 index 00000000..b71b85d7 --- /dev/null +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -0,0 +1,14 @@ +from marshmallow.fields import DateTime, Integer +from teal.marshmallow import SanitizedStr + +from ereuse_devicehub.resources.schemas import Thing +from ereuse_devicehub.resources.tradedocument import models as m + + +class Document(Thing): + __doc__ = m.Document.__doc__ + id = Integer(description=m.Document.id.comment, dump_only=True) + date = DateTime(required=False, description=m.Document.date.comment) + id_document = SanitizedStr(default='', description=m.Document.id_document.comment) + description = SanitizedStr(default='', description=m.Document.description.comment) + file_name = SanitizedStr(default='', description=m.Document.file_name.comment) diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py new file mode 100644 index 00000000..cc30e228 --- /dev/null +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -0,0 +1,35 @@ + +import marshmallow +from flask import g, current_app as app, render_template, request, Response +from flask.json import jsonify +from flask_sqlalchemy import Pagination +from marshmallow import fields, fields as f, validate as v, Schema as MarshmallowSchema +from teal.resource import View + +from ereuse_devicehub import auth +from ereuse_devicehub.db import db +from ereuse_devicehub.query import SearchQueryParser, things_response +from ereuse_devicehub.resources.tradedocument.models import Document + +class DocumentView(View): + + # @auth.Auth.requires_auth + def one(self, id: str): + document = Document.query.filter_by(id=id).first() + return self.schema.jsonify(document) + + # @auth.Auth.requires_auth + def post(self): + """Posts an action.""" + json = request.get_json(validate=False) + resource_def = app.resources[json['type']] + + a = resource_def.schema.load(json) + Model = db.Model._decl_class_registry.data[json['type']]() + action = Model(**a) + db.session.add(action) + db.session().final_flush() + ret = self.schema.jsonify(action) + ret.status_code = 201 + db.session.commit() + return ret diff --git a/tests/test_trade.py b/tests/test_trade.py index 98d98ac7..587dfb23 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -56,7 +56,6 @@ def test_offer_without_to(user: UserClient): 'userFrom': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': False, 'code': 'MAX' @@ -84,7 +83,6 @@ def test_offer_without_to(user: UserClient): 'userFrom': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': False, 'code': 'MAX' @@ -107,7 +105,6 @@ def test_offer_without_to(user: UserClient): 'userFrom': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot2.id, 'confirm': False, 'code': 'MAX' @@ -138,7 +135,6 @@ def test_offer_without_from(user: UserClient, user2: UserClient): 'userTo': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot.id, 'confirm': False, 'code': 'MAX' @@ -183,7 +179,6 @@ def test_offer_without_users(user: UserClient): 'devices': [device.id], 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot.id, 'confirm': False, 'code': 'MAX' @@ -217,7 +212,6 @@ def test_offer(user: UserClient): 'userTo': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot.id, 'confirm': True, } @@ -244,7 +238,6 @@ def test_offer_without_devices(user: UserClient): 'userTo': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': True, } @@ -272,7 +265,6 @@ def test_endpoint_confirm(user: UserClient, user2: UserClient): 'userTo': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': True, } @@ -313,7 +305,6 @@ def test_confirm_revoke(user: UserClient, user2: UserClient): 'userTo': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': True, } @@ -392,7 +383,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'userTo': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': True, } @@ -580,7 +570,6 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): 'userTo': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': True, } From 5e36cca3402b7bb3310775730c4804c1d77b8b9a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 13 May 2021 18:41:20 +0200 Subject: [PATCH 05/31] basic test of model Document --- tests/test_trade.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_trade.py b/tests/test_trade.py index 587dfb23..42de8874 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -28,6 +28,8 @@ from ereuse_devicehub.resources.device import states from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \ RamModule, SolidStateDrive from ereuse_devicehub.resources.enums import ComputerChassis, Severity, TestDataStorageLength +from ereuse_devicehub.resources.tradedocument.models import Document + from tests import conftest from tests.conftest import create_user, file @@ -658,10 +660,17 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): assert device_10.actions[-3].t == 'Revoke' +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_add_document_to_lot(user: UserClient, user2: UserClient): + """Example of one document inserted into one lot""" + lot, _ = user.post({'name': 'MyLot'}, res=Lot) @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_add_documentto_lot(user: UserClient, user2: UserClient): +def test_simple_add_document(user: UserClient): """Example of one document inserted into one lot""" - lot, _ = user.post({'name': 'MyLot'}, res=Lot) + doc = Document(**{'file_name': 'test', 'owner_id': user.user['id']}) + db.session.add(doc) + db.session.flush() From 6fc0e7183389d9671857aaf0171eadeb0e98de36 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 14 May 2021 12:57:54 +0200 Subject: [PATCH 06/31] new endpoint tests of tradedocuments --- tests/test_trade.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/test_trade.py b/tests/test_trade.py index 42de8874..60db9882 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -28,7 +28,7 @@ from ereuse_devicehub.resources.device import states from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \ RamModule, SolidStateDrive from ereuse_devicehub.resources.enums import ComputerChassis, Severity, TestDataStorageLength -from ereuse_devicehub.resources.tradedocument.models import Document +from ereuse_devicehub.resources.tradedocument.models import TradeDocument from tests import conftest from tests.conftest import create_user, file @@ -662,15 +662,31 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_add_document_to_lot(user: UserClient, user2: UserClient): +def test_simple_add_document(user: UserClient): """Example of one document inserted into one lot""" - lot, _ = user.post({'name': 'MyLot'}, res=Lot) + doc = TradeDocument(**{'file_name': 'test', 'owner_id': user.user['id']}) + db.session.add(doc) + db.session.flush() @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_simple_add_document(user: UserClient): +def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client): """Example of one document inserted into one lot""" - doc = Document(**{'file_name': 'test', 'owner_id': user.user['id']}) - db.session.add(doc) - db.session.flush() + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + # data = {'file_name': 'test', 'lot': lot['id']} + data = {'file_name': 'test'} + doc, _ = user.post(res=TradeDocument, data=data) + user.get(res=TradeDocument, item=doc['id']) + user.delete(res=TradeDocument, item=doc['id']) + + # check permitions + doc, _ = user.post(res=TradeDocument, data=data) + + # anonyms users + client.get(res=TradeDocument, item=doc['id'], status=401) + client.delete(res=TradeDocument, item=doc['id'], status=401) + + # other user + user2.get(res=TradeDocument, item=doc['id'], status=404) + user2.delete(res=TradeDocument, item=doc['id'], status=404) From 4b113727197771e3a36e2795d55a24fe6fbe64de Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 14 May 2021 12:58:56 +0200 Subject: [PATCH 07/31] Definition of endpoint, view and scheme for basic document --- ereuse_devicehub/config.py | 2 ++ ereuse_devicehub/resources/action/models.py | 22 +++++++------- .../resources/tradedocument/__init__.py | 10 ------- .../resources/tradedocument/definitions.py | 10 +++++++ .../resources/tradedocument/models.py | 2 +- .../resources/tradedocument/schemas.py | 15 +++++----- .../resources/tradedocument/views.py | 30 +++++++++---------- 7 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 ereuse_devicehub/resources/tradedocument/definitions.py diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py index 02d97da3..e9d1373d 100644 --- a/ereuse_devicehub/config.py +++ b/ereuse_devicehub/config.py @@ -12,6 +12,7 @@ from ereuse_devicehub.resources import action, agent, deliverynote, inventory, \ lot, tag, user from ereuse_devicehub.resources.device import definitions from ereuse_devicehub.resources.documents import documents +from ereuse_devicehub.resources.tradedocument import definitions as tradedocument from ereuse_devicehub.resources.enums import PriceSoftware from ereuse_devicehub.resources.versions import versions from ereuse_devicehub.resources.licences import licences @@ -27,6 +28,7 @@ class DevicehubConfig(Config): import_resource(lot), import_resource(deliverynote), import_resource(documents), + import_resource(tradedocument), import_resource(inventory), import_resource(versions), import_resource(licences), diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index fe23d2e2..5250469d 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -48,7 +48,7 @@ from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, Bio TestDataStorageLength from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing from ereuse_devicehub.resources.user.models import User -from ereuse_devicehub.resources.tradedocument.models import Document +from ereuse_devicehub.resources.tradedocument.models import TradeDocument class JoinedTableMixin: @@ -296,18 +296,17 @@ class ActionDevice(db.Model): primary_key=True) -class ActionWithMultipleDocuments(ActionWithMultipleDevices): - # pass - documents = relationship(Document, +class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): + documents = relationship(TradeDocument, backref=backref('actions_multiple_docs', lazy=True, **_sorted_actions), - secondary=lambda: ActionDocument.__table__, - order_by=lambda: Document.id, + secondary=lambda: ActionTradeDocument.__table__, + order_by=lambda: TradeDocument.id, collection_class=OrderedSet) -class ActionDocument(db.Model): - document_id = Column(BigInteger, ForeignKey(Document.id), primary_key=True) - action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDocuments.id), +class ActionTradeDocument(db.Model): + document_id = Column(BigInteger, ForeignKey(TradeDocument.id), primary_key=True) + action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleTradeDocuments.id), primary_key=True) @@ -1449,7 +1448,7 @@ class CancelReservation(Organize): """The act of cancelling a reservation.""" -class Confirm(JoinedTableMixin, ActionWithMultipleDocuments): +class Confirm(JoinedTableMixin, ActionWithMultipleTradeDocuments): """Users confirm the one action trade this confirmation it's link to trade and the devices that confirm """ @@ -1489,7 +1488,7 @@ class ConfirmRevoke(Confirm): return '<{0.t} {0.id} accepted by {0.user}>'.format(self) -class Trade(JoinedTableMixin, ActionWithMultipleDocuments): +class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments): """Trade actions log the political exchange of devices between users. Every time a trade action is performed, the old user looses its political possession, for example ownership, in favor of another @@ -1502,7 +1501,6 @@ class Trade(JoinedTableMixin, ActionWithMultipleDocuments): This class and its inheritors extend `Schema's Trade `_. """ - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) user_from_id = db.Column(UUID(as_uuid=True), db.ForeignKey(User.id), nullable=False) diff --git a/ereuse_devicehub/resources/tradedocument/__init__.py b/ereuse_devicehub/resources/tradedocument/__init__.py index 0e75f6e8..e69de29b 100644 --- a/ereuse_devicehub/resources/tradedocument/__init__.py +++ b/ereuse_devicehub/resources/tradedocument/__init__.py @@ -1,10 +0,0 @@ -from teal.resource import Converters, Resource - -from ereuse_devicehub.resources.tradedocument import schemas -from ereuse_devicehub.resources.tradedocument.views import DocumentView - -class TradeDocumentDef(Resource): - SCHEMA = schemas.Document - VIEW = DocumentView - AUTH = True - ID_CONVERTER = Converters.string diff --git a/ereuse_devicehub/resources/tradedocument/definitions.py b/ereuse_devicehub/resources/tradedocument/definitions.py new file mode 100644 index 00000000..e321c7b4 --- /dev/null +++ b/ereuse_devicehub/resources/tradedocument/definitions.py @@ -0,0 +1,10 @@ +from teal.resource import Converters, Resource + +from ereuse_devicehub.resources.tradedocument import schemas +from ereuse_devicehub.resources.tradedocument.views import TradeDocumentView + +class TradeDocumentDef(Resource): + SCHEMA = schemas.TradeDocument + VIEW = TradeDocumentView + AUTH = True + ID_CONVERTER = Converters.string diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 85db108b..d4274fd8 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -24,7 +24,7 @@ from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, Co DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState -class Document(Thing): +class TradeDocument(Thing): """This represent a document involved in a trade action. Every document is added to a lot. When this lot is converted in one trade, the action trade is added to the document diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py index b71b85d7..86c2e213 100644 --- a/ereuse_devicehub/resources/tradedocument/schemas.py +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -5,10 +5,11 @@ from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.tradedocument import models as m -class Document(Thing): - __doc__ = m.Document.__doc__ - id = Integer(description=m.Document.id.comment, dump_only=True) - date = DateTime(required=False, description=m.Document.date.comment) - id_document = SanitizedStr(default='', description=m.Document.id_document.comment) - description = SanitizedStr(default='', description=m.Document.description.comment) - file_name = SanitizedStr(default='', description=m.Document.file_name.comment) +class TradeDocument(Thing): + __doc__ = m.TradeDocument.__doc__ + id = Integer(description=m.TradeDocument.id.comment, dump_only=True) + date = DateTime(required=False, description=m.TradeDocument.date.comment) + id_document = SanitizedStr(default='', description=m.TradeDocument.id_document.comment) + description = SanitizedStr(default='', description=m.TradeDocument.description.comment) + file_name = SanitizedStr(default='', description=m.TradeDocument.file_name.comment) + # lot = NestedOn('Lot', dump_only=True, description=m.TradeDocument.lot.__doc__) diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index cc30e228..69b206c9 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -9,27 +9,27 @@ from teal.resource import View from ereuse_devicehub import auth from ereuse_devicehub.db import db from ereuse_devicehub.query import SearchQueryParser, things_response -from ereuse_devicehub.resources.tradedocument.models import Document +from ereuse_devicehub.resources.tradedocument.models import TradeDocument -class DocumentView(View): +class TradeDocumentView(View): - # @auth.Auth.requires_auth def one(self, id: str): - document = Document.query.filter_by(id=id).first() - return self.schema.jsonify(document) + doc = TradeDocument.query.filter_by(id=id, owner=g.user).one() + return self.schema.jsonify(doc) - # @auth.Auth.requires_auth def post(self): - """Posts an action.""" - json = request.get_json(validate=False) - resource_def = app.resources[json['type']] - - a = resource_def.schema.load(json) - Model = db.Model._decl_class_registry.data[json['type']]() - action = Model(**a) - db.session.add(action) + """Add one document.""" + data = request.get_json(validate=True) + doc = TradeDocument(**data) + db.session.add(doc) db.session().final_flush() - ret = self.schema.jsonify(action) + ret = self.schema.jsonify(doc) ret.status_code = 201 db.session.commit() return ret + + def delete(self, id): + doc = TradeDocument.query.filter_by(id=id, owner=g.user).one() + db.session.delete(doc) + db.session.commit() + return Response(status=204) From 3b8b14cfba5a251fcd7dd474133b75221ebb0f85 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 19 May 2021 09:45:01 +0200 Subject: [PATCH 08/31] fixing test endpoint docs --- tests/test_trade.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/test_trade.py b/tests/test_trade.py index 60db9882..dd7aee8c 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -1,4 +1,5 @@ import os +import base64 import ipaddress import json import shutil @@ -20,6 +21,7 @@ from ereuse_devicehub.db import db from ereuse_devicehub.client import UserClient, Client from ereuse_devicehub.devicehub import Devicehub from ereuse_devicehub.resources import enums +from ereuse_devicehub.resources.hash_reports import ReportHash from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.agent.models import Person from ereuse_devicehub.resources.lot.models import Lot @@ -671,12 +673,23 @@ def test_simple_add_document(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client): +def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client, app: Devicehub): """Example of one document inserted into one lot""" lot, _ = user.post({'name': 'MyLot'}, res=Lot) - # data = {'file_name': 'test', 'lot': lot['id']} - data = {'file_name': 'test'} - doc, _ = user.post(res=TradeDocument, data=data) + data = {'lot': lot['id'], 'file_name': 'test.csv'} + base64_bytes = base64.b64encode(b'This is a test') + base64_string = base64_bytes.decode('utf-8') + data['file'] = base64_string + doc, _ = user.post(res=TradeDocument, + data=data) + + assert len(ReportHash.query.all()) == 1 + + path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user.email) + path = os.path.join(path_dir_base, lot['id']) + assert len(os.listdir(path)) == 1 + # import pdb; pdb.set_trace() + user.get(res=TradeDocument, item=doc['id']) user.delete(res=TradeDocument, item=doc['id']) @@ -690,3 +703,4 @@ def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client # other user user2.get(res=TradeDocument, item=doc['id'], status=404) user2.delete(res=TradeDocument, item=doc['id'], status=404) + shutil.rmtree(path) From 020e6afd530b0c8ca49d0194b6505df6eb486b85 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 19 May 2021 09:47:14 +0200 Subject: [PATCH 09/31] saved phisical documents to disk --- .../resources/tradedocument/models.py | 31 +++++++----- .../resources/tradedocument/schemas.py | 21 ++++++-- .../resources/tradedocument/views.py | 48 +++++++++++++++---- 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index d4274fd8..409d34eb 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -7,6 +7,7 @@ from flask import current_app as app, g from sqlalchemy.dialects.postgresql import UUID from ereuse_devicehub.db import db from ereuse_devicehub.resources.user.models import User +from sortedcontainers import SortedSet from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time from sqlalchemy import BigInteger, Boolean, Column, Float, ForeignKey, Integer, \ @@ -15,7 +16,7 @@ from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import ColumnProperty, backref, relationship, validates from sqlalchemy.util import OrderedSet from sqlalchemy_utils import ColorType -from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, \ +from teal.db import CASCADE_OWN, CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, \ check_lower, check_range from teal.resource import url_for_resource @@ -24,6 +25,11 @@ from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, Co DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState +_sorted_documents = { + 'order_by': lambda: TradeDocument.created, + 'collection_class': SortedSet +} + class TradeDocument(Thing): """This represent a document involved in a trade action. Every document is added to a lot. @@ -59,10 +65,20 @@ class TradeDocument(Thing): nullable=False, default=lambda: g.user.id) owner = db.relationship(User, primaryjoin=owner_id == User.id) + lot_id = db.Column(UUID(as_uuid=True), + db.ForeignKey('lot.id'), + nullable=False) + lot = db.relationship('Lot', + backref=backref('documents', + lazy=True, + cascade=CASCADE_OWN, + **_sorted_documents), + primaryjoin='TradeDocument.lot_id == Lot.id') + lot.comment = """Lot to which the document is associated""" file_name = Column(db.CIText()) file_name.comment = """This is the name of the file when user up the document.""" - file_name_disk = Column(db.CIText()) - file_name_disk.comment = """This is the name of the file as devicehub save in server.""" + path_name = Column(db.CIText()) + path_name.comment = """This is the name of the file as devicehub save in server.""" __table_args__ = ( db.Index('document_id', id, postgresql_using='hash'), @@ -81,15 +97,6 @@ class TradeDocument(Thing): """ return sorted(self.actions_multiple_docs, key=lambda x: x.created) - @property - def path_to_file(self) -> str: - """The path of one file is defined by the owner, file_name and created time. - - """ - base = app.config['PATH_DOCUMENTS_STORAGE'] - file_name = "{0.date}-{0.filename}".format(self) - base = os.path.join(base, g.user.email, file_name) - return sorted(self.actions_multiple_docs, key=lambda x: x.created) def last_action_of(self, *types): """Gets the last action of the given types. diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py index 86c2e213..549017c4 100644 --- a/ereuse_devicehub/resources/tradedocument/schemas.py +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -1,8 +1,13 @@ -from marshmallow.fields import DateTime, Integer -from teal.marshmallow import SanitizedStr +import base64 +from marshmallow.fields import DateTime, Integer, Raw +from teal.marshmallow import SanitizedStr +from marshmallow import ValidationError, validates_schema + +from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.tradedocument import models as m +from ereuse_devicehub.resources.lot import schemas as s_lot class TradeDocument(Thing): @@ -12,4 +17,14 @@ class TradeDocument(Thing): id_document = SanitizedStr(default='', description=m.TradeDocument.id_document.comment) description = SanitizedStr(default='', description=m.TradeDocument.description.comment) file_name = SanitizedStr(default='', description=m.TradeDocument.file_name.comment) - # lot = NestedOn('Lot', dump_only=True, description=m.TradeDocument.lot.__doc__) + file = Raw(type='file') + lot = NestedOn(s_lot.Lot, only_query='id', description=m.TradeDocument.lot.__doc__) + + + @validates_schema + def validate_filestream(self, data): + if not data.get('file'): + txt = 'Error, no there are any file for save' + raise ValidationError(txt) + + data['file'] = base64.b64decode(data['file']) diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index 69b206c9..212c6264 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -1,15 +1,40 @@ - -import marshmallow -from flask import g, current_app as app, render_template, request, Response -from flask.json import jsonify -from flask_sqlalchemy import Pagination -from marshmallow import fields, fields as f, validate as v, Schema as MarshmallowSchema +import os +from datetime import datetime +from flask import current_app as app, request, g, Response +from marshmallow import ValidationError from teal.resource import View -from ereuse_devicehub import auth from ereuse_devicehub.db import db -from ereuse_devicehub.query import SearchQueryParser, things_response from ereuse_devicehub.resources.tradedocument.models import TradeDocument +from ereuse_devicehub.resources.hash_reports import insert_hash + + +def save_doc(data, user): + """ + This function allow save a snapshot in json format un a TMP_SNAPSHOTS directory + The file need to be saved with one name format with the stamptime and uuid joins + """ + filename = data['file_name'] + lot = data['lot'] + now = datetime.now() + year = now.year + month = now.month + day = now.day + hour = now.hour + minutes = now.minute + + name_file = f"{year}-{month}-{day}-{hour}-{minutes}_{user}_{filename}" + path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user) + path = os.path.join(path_dir_base, str(lot.id)) + path_name = os.path.join(path, name_file) + + os.system(f'mkdir -p {path}') + + with open(path_name, 'wb') as doc_file: + doc_file.write(data['file']) + + return path_name + class TradeDocumentView(View): @@ -19,7 +44,14 @@ class TradeDocumentView(View): def post(self): """Add one document.""" + # import pdb; pdb.set_trace() + data = request.get_json(validate=True) + data['path_name'] = save_doc(data, g.user.email) + bfile = data.pop('file') + insert_hash(bfile) + + # import pdb; pdb.set_trace() doc = TradeDocument(**data) db.session.add(doc) db.session().final_flush() From 443464c9b80c30db256226871f874a3c8713024b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 19 May 2021 13:58:39 +0200 Subject: [PATCH 10/31] trade test for documents --- tests/test_trade.py | 207 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 194 insertions(+), 13 deletions(-) diff --git a/tests/test_trade.py b/tests/test_trade.py index dd7aee8c..690ad9ed 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -21,7 +21,7 @@ from ereuse_devicehub.db import db from ereuse_devicehub.client import UserClient, Client from ereuse_devicehub.devicehub import Devicehub from ereuse_devicehub.resources import enums -from ereuse_devicehub.resources.hash_reports import ReportHash +from ereuse_devicehub.resources.hash_reports import ReportHash from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.agent.models import Person from ereuse_devicehub.resources.lot.models import Lot @@ -378,7 +378,7 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): item='{}/devices'.format(lot['id']), query=devices[:7]) - # the manager shares the temporary lot with the SCRAP as an incoming lot + # the manager shares the temporary lot with the SCRAP as an incoming lot # for the CRAP to confirm it request_post = { 'type': 'Trade', @@ -394,17 +394,19 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): user.post(res=models.Action, data=request_post) trade = models.Trade.query.one() + assert trade.devices[0].actions[-2].t == 'Trade' + assert trade.devices[0].actions[-1].t == 'Confirm' + assert trade.devices[0].actions[-1].user == trade.user_to + # the SCRAP confirms 3 of the 10 devices in its outgoing lot request_confirm = { 'type': 'Confirm', 'action': trade.id, 'devices': [snap1['device']['id'], snap2['device']['id'], snap3['device']['id']] } - assert trade.devices[0].actions[-2].t == 'Trade' - assert trade.devices[0].actions[-1].t == 'Confirm' - assert trade.devices[0].actions[-1].user == trade.user_to - + # import pdb; pdb.set_trace() user2.post(res=models.Action, data=request_confirm) + assert trade.devices[0].actions[-1].t == 'Confirm' assert trade.devices[0].actions[-1].user == trade.user_from n_actions = len(trade.devices[0].actions) @@ -438,8 +440,8 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'type': 'Confirm', 'action': trade.id, 'devices': [ - snap1['device']['id'], - snap2['device']['id'], + snap1['device']['id'], + snap2['device']['id'], snap3['device']['id'], snap4['device']['id'], snap5['device']['id'], @@ -457,7 +459,7 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): assert trade.devices[-1].actions[-1].user == trade.user_from assert len(trade.devices[0].actions) == n_actions - # The manager remove one device of the lot and automaticaly + # The manager remove one device of the lot and automaticaly # is create one revoke action device_10 = trade.devices[-1] lot, _ = user.delete({}, @@ -565,7 +567,7 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): item='{}/devices'.format(lot['id']), query=devices) - # the manager shares the temporary lot with the SCRAP as an incoming lot + # the manager shares the temporary lot with the SCRAP as an incoming lot # for the CRAP to confirm it request_post = { 'type': 'Trade', @@ -586,8 +588,8 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): 'type': 'Confirm', 'action': trade.id, 'devices': [ - snap1['device']['id'], - snap2['device']['id'], + snap1['device']['id'], + snap2['device']['id'], snap3['device']['id'], snap4['device']['id'], snap5['device']['id'], @@ -604,7 +606,7 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): assert trade.devices[-1].actions[-1].t == 'Confirm' assert trade.devices[-1].actions[-1].user == trade.user_from - # The manager remove one device of the lot and automaticaly + # The manager remove one device of the lot and automaticaly # is create one revoke action device_10 = trade.devices[-1] lot, _ = user.delete({}, @@ -704,3 +706,182 @@ def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client user2.get(res=TradeDocument, item=doc['id'], status=404) user2.delete(res=TradeDocument, item=doc['id'], status=404) shutil.rmtree(path) + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_usecase1_document(user: UserClient, user2: UserClient): + """Example of one usecase about confirmation""" + # the pRp (manatest_usecase_confirmationger) creates a temporary lot + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user.email) + path = os.path.join(path_dir_base, lot['id']) + data = {'lot': lot['id'], 'file_name': 'test.csv'} + base64_bytes = base64.b64encode(b'This is a test') + base64_string = base64_bytes.decode('utf-8') + data['file'] = base64_string + user.post(res=TradeDocument, data=data) + user.post(res=TradeDocument, data=data) + + # the manager shares the temporary lot with the SCRAP as an incoming lot + # for the CRAP to confirm it + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user2.email, + 'userTo': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + assert trade.documents[0].actions[-2].t == 'Trade' + assert trade.documents[0].actions[-1].t == 'Confirm' + assert trade.documents[0].actions[-1].user == trade.user_to + assert trade.documents[1].actions[-2].t == 'Trade' + assert trade.documents[1].actions[-1].t == 'Confirm' + assert trade.documents[1].actions[-1].user == trade.user_to + assert trade.documents[0].path_name != trade.documents[1].path_name + + # ======== + # import pdb; pdb.set_trace() + # the SCRAP confirms 1 of the 2 documents in its outgoing lot + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'documents': [trade.documents[0].id] + } + + user2.post(res=models.Action, data=request_confirm) + assert trade.documents[0].actions[-1].t == 'Confirm' + assert trade.documents[0].actions[-1].user == trade.user_from + n_actions = len(trade.documents[0].actions) + + # check validation error + # request_confirm = { + # 'type': 'Confirm', + # 'action': trade.id, + # 'devices': [ + # snap10['device']['id'] + # ] + # } + + # user2.post(res=models.Action, data=request_confirm, status=422) + + + # ======== + shutil.rmtree(path) + """ + # The manager add 3 device more into the lot + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[7:]) + + assert trade.devices[-1].actions[-2].t == 'Trade' + assert trade.devices[-1].actions[-1].t == 'Confirm' + assert trade.devices[-1].actions[-1].user == trade.user_to + assert len(trade.devices[0].actions) == n_actions + + + # the SCRAP confirms the rest of devices + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap1['device']['id'], + snap2['device']['id'], + snap3['device']['id'], + snap4['device']['id'], + snap5['device']['id'], + snap6['device']['id'], + snap7['device']['id'], + snap8['device']['id'], + snap9['device']['id'], + snap10['device']['id'] + ] + } + + user2.post(res=models.Action, data=request_confirm) + assert trade.devices[-1].actions[-3].t == 'Trade' + assert trade.devices[-1].actions[-1].t == 'Confirm' + assert trade.devices[-1].actions[-1].user == trade.user_from + assert len(trade.devices[0].actions) == n_actions + + # The manager remove one device of the lot and automaticaly + # is create one revoke action + device_10 = trade.devices[-1] + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) + assert len(trade.lot.devices) == len(trade.devices) == 9 + assert not device_10 in trade.devices + assert device_10.actions[-1].t == 'Revoke' + + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) + + assert device_10.actions[-1].t == 'Revoke' + assert device_10.actions[-2].t == 'Confirm' + + # the SCRAP confirms the revoke action + request_confirm_revoke = { + 'type': 'ConfirmRevoke', + 'action': device_10.actions[-1].id, + 'devices': [ + snap10['device']['id'] + ] + } + + user2.post(res=models.Action, data=request_confirm_revoke) + assert device_10.actions[-1].t == 'ConfirmRevoke' + assert device_10.actions[-2].t == 'Revoke' + + request_confirm_revoke = { + 'type': 'ConfirmRevoke', + 'action': device_10.actions[-1].id, + 'devices': [ + snap9['device']['id'] + ] + } + + # check validation error + user2.post(res=models.Action, data=request_confirm_revoke, status=422) + + + # The manager add again device_10 + assert len(trade.devices) == 9 + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:]) + + assert device_10.actions[-1].t == 'Confirm' + assert device_10 in trade.devices + assert len(trade.devices) == 10 + + + # the SCRAP confirms the action trade for device_10 + request_reconfirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [ + snap10['device']['id'] + ] + } + user2.post(res=models.Action, data=request_reconfirm) + assert device_10.actions[-1].t == 'Confirm' + assert device_10.actions[-1].user == trade.user_from + assert device_10.actions[-2].t == 'Confirm' + assert device_10.actions[-2].user == trade.user_to + assert device_10.actions[-3].t == 'ConfirmRevoke' + assert len(device_10.actions) == 13 + + """ From 28da6a953d6862341e002f659935bf4e2272bd2f Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 19 May 2021 13:59:59 +0200 Subject: [PATCH 11/31] adding trade for documents --- ereuse_devicehub/resources/action/schemas.py | 6 ++++ .../resources/action/views/trade.py | 28 +++++++++++-------- .../resources/tradedocument/views.py | 7 ++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index a8c7df85..fb33cfea 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -528,7 +528,13 @@ class Trade(ActionWithMultipleDevices): txt = "you need to be the owner of the lot for to do a trade" raise ValidationError(txt) + for doc in data['lot'].documents: + if not doc.owner == g.user: + txt = "you need to be the owner of the documents for to do a trade" + raise ValidationError(txt) + data['devices'] = data['lot'].devices + data['documents'] = data['lot'].documents @validates_schema def validate_user_to_id(self, data: dict): diff --git a/ereuse_devicehub/resources/action/views/trade.py b/ereuse_devicehub/resources/action/views/trade.py index 57ecc665..8b22de17 100644 --- a/ereuse_devicehub/resources/action/views/trade.py +++ b/ereuse_devicehub/resources/action/views/trade.py @@ -11,15 +11,15 @@ from ereuse_devicehub.resources.user.models import User class TradeView(): """Handler for manager the trade action register from post - + request_post = { 'type': 'Trade', 'devices': [device_id], + 'documents': [document_id], 'userFrom': user2.email, 'userTo': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirm': True, } @@ -27,6 +27,7 @@ class TradeView(): """ def __init__(self, data, resource_def, schema): + # import pdb; pdb.set_trace() self.schema = schema a = resource_def.schema.load(data) self.trade = Trade(**a) @@ -36,7 +37,6 @@ class TradeView(): self.create_confirmations() def post(self): - # import pdb; pdb.set_trace() db.session().final_flush() ret = self.schema.jsonify(self.trade) ret.status_code = 201 @@ -49,21 +49,25 @@ class TradeView(): # if the confirmation is mandatory, do automatic confirmation only for # owner of the lot if self.trade.confirm: - confirm = Confirm(user=g.user, - action=self.trade, - devices=self.trade.devices) - db.session.add(confirm) + confirm_devs = Confirm(user=g.user, + action=self.trade, + devices=self.trade.devices) + + confirm_docs = Confirm(user=g.user, + action=self.trade, + documents=self.trade.documents) + db.session.add(confirm_devs, confirm_docs) return # check than the user than want to do the action is one of the users # involved in the action assert g.user.id in [self.trade.user_from_id, self.trade.user_to_id] - confirm_from = Confirm(user=self.trade.user_from, - action=self.trade, + confirm_from = Confirm(user=self.trade.user_from, + action=self.trade, devices=self.trade.devices) - confirm_to = Confirm(user=self.trade.user_to, - action=self.trade, + confirm_to = Confirm(user=self.trade.user_to, + action=self.trade, devices=self.trade.devices) db.session.add(confirm_from) db.session.add(confirm_to) @@ -292,7 +296,7 @@ class ConfirmRevokeView(ConfirmMixin): # Change the owner for every devices trade = data['action'] for dev in data['devices']: - # TODO @cayop this should be the author of confirm actions instead of + # TODO @cayop this should be the author of confirm actions instead of # author of trade dev.owner = trade.author if hasattr(dev, 'components'): diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index 212c6264..e85db306 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -1,7 +1,7 @@ import os +import time from datetime import datetime from flask import current_app as app, request, g, Response -from marshmallow import ValidationError from teal.resource import View from ereuse_devicehub.db import db @@ -22,8 +22,9 @@ def save_doc(data, user): day = now.day hour = now.hour minutes = now.minute + created = time.time() - name_file = f"{year}-{month}-{day}-{hour}-{minutes}_{user}_{filename}" + name_file = f"{year}-{month}-{day}-{hour}-{minutes}_{created}_{user}_{filename}" path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user) path = os.path.join(path_dir_base, str(lot.id)) path_name = os.path.join(path, name_file) @@ -44,14 +45,12 @@ class TradeDocumentView(View): def post(self): """Add one document.""" - # import pdb; pdb.set_trace() data = request.get_json(validate=True) data['path_name'] = save_doc(data, g.user.email) bfile = data.pop('file') insert_hash(bfile) - # import pdb; pdb.set_trace() doc = TradeDocument(**data) db.session.add(doc) db.session().final_flush() From 74dd300cad8e7c0446c24a1c5a9db036812bbe75 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 21 May 2021 13:16:06 +0200 Subject: [PATCH 12/31] finish tests --- tests/test_basic.py | 3 +- tests/test_trade.py | 285 ++++++++++++++++++++++++-------------------- 2 files changed, 161 insertions(+), 127 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 00c9cf93..32125937 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -54,6 +54,7 @@ def test_api_docs(client: Client): '/metrics/', '/tags/', '/tags/{tag_id}/device/{device_id}', + '/trade-documents/', '/users/', '/users/login/' # '/devices/{dev1_id}/merge/{dev2_id}', @@ -119,4 +120,4 @@ def test_api_docs(client: Client): 'scheme': 'basic', 'name': 'Authorization' } - assert len(docs['definitions']) == 120 + assert len(docs['definitions']) == 122 diff --git a/tests/test_trade.py b/tests/test_trade.py index 690ad9ed..f2fc2b07 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -281,7 +281,8 @@ def test_endpoint_confirm(user: UserClient, user2: UserClient): request_confirm = { 'type': 'Confirm', 'action': trade.id, - 'devices': [device_id] + 'devices': [device_id], + 'documents': [] } user2.post(res=models.Action, data=request_confirm) @@ -319,13 +320,15 @@ def test_confirm_revoke(user: UserClient, user2: UserClient): request_confirm = { 'type': 'Confirm', 'action': trade.id, - 'devices': [device_id] + 'devices': [device_id], + 'documents': [] } request_revoke = { 'type': 'Revoke', 'action': trade.id, 'devices': [device_id], + 'documents': [] } @@ -402,9 +405,9 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): request_confirm = { 'type': 'Confirm', 'action': trade.id, - 'devices': [snap1['device']['id'], snap2['device']['id'], snap3['device']['id']] + 'devices': [snap1['device']['id'], snap2['device']['id'], snap3['device']['id']], + 'documents': [] } - # import pdb; pdb.set_trace() user2.post(res=models.Action, data=request_confirm) assert trade.devices[0].actions[-1].t == 'Confirm' @@ -417,7 +420,8 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'action': trade.id, 'devices': [ snap10['device']['id'] - ] + ], + 'documents': [] } user2.post(res=models.Action, data=request_confirm, status=422) @@ -484,7 +488,8 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'action': device_10.actions[-1].id, 'devices': [ snap10['device']['id'] - ] + ], + 'documents': [] } user2.post(res=models.Action, data=request_confirm_revoke) @@ -496,7 +501,8 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'action': device_10.actions[-1].id, 'devices': [ snap9['device']['id'] - ] + ], + 'documents': [] } # check validation error @@ -521,9 +527,9 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'action': trade.id, 'devices': [ snap10['device']['id'] - ] + ], + 'documents': [] } - # import pdb; pdb.set_trace() user2.post(res=models.Action, data=request_reconfirm) assert device_10.actions[-1].t == 'Confirm' assert device_10.actions[-1].user == trade.user_from @@ -610,9 +616,9 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): # is create one revoke action device_10 = trade.devices[-1] lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices[-1:], status=200) assert len(trade.lot.devices) == len(trade.devices) == 9 assert not device_10 in trade.devices assert device_10.actions[-1].t == 'Revoke' @@ -642,7 +648,8 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): 'action': device_10.actions[-2].id, 'devices': [ snap10['device']['id'] - ] + ], + 'documents': [] } # check validation error @@ -654,7 +661,8 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): 'action': trade.id, 'devices': [ snap10['device']['id'] - ] + ], + 'documents': [] } user2.post(res=models.Action, data=request_reconfirm) assert device_10.actions[-1].t == 'Confirm' @@ -668,7 +676,10 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): @pytest.mark.usefixtures(conftest.app_context.__name__) def test_simple_add_document(user: UserClient): """Example of one document inserted into one lot""" - doc = TradeDocument(**{'file_name': 'test', 'owner_id': user.user['id']}) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + doc = TradeDocument(**{'file_name': 'test', + 'owner_id': user.user['id'], + 'lot_id': lot['id']}) db.session.add(doc) db.session.flush() @@ -690,7 +701,6 @@ def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user.email) path = os.path.join(path_dir_base, lot['id']) assert len(os.listdir(path)) == 1 - # import pdb; pdb.set_trace() user.get(res=TradeDocument, item=doc['id']) user.delete(res=TradeDocument, item=doc['id']) @@ -714,8 +724,6 @@ def test_usecase1_document(user: UserClient, user2: UserClient): """Example of one usecase about confirmation""" # the pRp (manatest_usecase_confirmationger) creates a temporary lot lot, _ = user.post({'name': 'MyLot'}, res=Lot) - path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user.email) - path = os.path.join(path_dir_base, lot['id']) data = {'lot': lot['id'], 'file_name': 'test.csv'} base64_bytes = base64.b64encode(b'This is a test') base64_string = base64_bytes.decode('utf-8') @@ -747,12 +755,11 @@ def test_usecase1_document(user: UserClient, user2: UserClient): assert trade.documents[1].actions[-1].user == trade.user_to assert trade.documents[0].path_name != trade.documents[1].path_name - # ======== - # import pdb; pdb.set_trace() # the SCRAP confirms 1 of the 2 documents in its outgoing lot request_confirm = { 'type': 'Confirm', 'action': trade.id, + 'devices': [], 'documents': [trade.documents[0].id] } @@ -762,126 +769,152 @@ def test_usecase1_document(user: UserClient, user2: UserClient): n_actions = len(trade.documents[0].actions) # check validation error - # request_confirm = { - # 'type': 'Confirm', - # 'action': trade.id, - # 'devices': [ - # snap10['device']['id'] - # ] - # } - - # user2.post(res=models.Action, data=request_confirm, status=422) - - - # ======== - shutil.rmtree(path) - """ - # The manager add 3 device more into the lot - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[7:]) - - assert trade.devices[-1].actions[-2].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_to - assert len(trade.devices[0].actions) == n_actions - - - # the SCRAP confirms the rest of devices request_confirm = { 'type': 'Confirm', 'action': trade.id, - 'devices': [ - snap1['device']['id'], - snap2['device']['id'], - snap3['device']['id'], - snap4['device']['id'], - snap5['device']['id'], - snap6['device']['id'], - snap7['device']['id'], - snap8['device']['id'], - snap9['device']['id'], - snap10['device']['id'] - ] + 'devices': [], + 'documents': [trade.documents[0].id], + } + + user2.post(res=models.Action, data=request_confirm, status=422) + + # Both users can up de documents + user2.post(res=TradeDocument, data=data) + assert len(trade.documents) == 3 + + # user1 confirm document2 + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [], + 'documents': [trade.documents[2].id], + } + + assert trade.documents[2].actions[-1].user == trade.user_from + assert trade.documents[2].actions[-1].t == 'Confirm' + + user.post(res=models.Action, data=request_confirm) + assert trade.documents[2].actions[-1].user == trade.user_to + assert trade.documents[2].actions[-1].t == 'Confirm' + + # clean docs of test + path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'], user.email) + path = os.path.join(path_dir_base, lot['id']) + + path_dir_base_from = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'], user2.email) + path_from = os.path.join(path_dir_base_from, lot['id']) + + shutil.rmtree(path) + shutil.rmtree(path_from) + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_document_confirm_revoke(user: UserClient, user2: UserClient): + """We want check one simple revoke""" + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + data = {'lot': lot['id'], 'file_name': 'test.csv'} + base64_bytes = base64.b64encode(b'This is a test') + base64_string = base64_bytes.decode('utf-8') + data['file'] = base64_string + user.post(res=TradeDocument, data=data) + + # the manager shares the temporary lot with the SCRAP as an incoming lot + # for the CRAP to confirm it + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user2.email, + 'userTo': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirm': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + # the SCRAP confirms 1 in its outgoing lot + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [], + 'documents': [trade.documents[0].id] } user2.post(res=models.Action, data=request_confirm) - assert trade.devices[-1].actions[-3].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_from - assert len(trade.devices[0].actions) == n_actions - # The manager remove one device of the lot and automaticaly - # is create one revoke action - device_10 = trade.devices[-1] - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - assert len(trade.lot.devices) == len(trade.devices) == 9 - assert not device_10 in trade.devices - assert device_10.actions[-1].t == 'Revoke' - - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - - assert device_10.actions[-1].t == 'Revoke' - assert device_10.actions[-2].t == 'Confirm' - - # the SCRAP confirms the revoke action - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, - 'devices': [ - snap10['device']['id'] - ] + # the SCRAP revoke the document + request_revoke = { + 'type': 'Revoke', + 'action': trade.id, + 'devices': [], + 'documents': [trade.documents[0].id] } - user2.post(res=models.Action, data=request_confirm_revoke) - assert device_10.actions[-1].t == 'ConfirmRevoke' - assert device_10.actions[-2].t == 'Revoke' + user2.post(res=models.Action, data=request_revoke) + assert trade.documents[0].actions[-1].t == 'Revoke' + assert trade.documents[0].actions[-2].t == 'Confirm' + user2.post(res=models.Action, data=request_revoke, status=422) - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, - 'devices': [ - snap9['device']['id'] - ] + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_document_confirm_revoke_confirmRevoke(user: UserClient, user2: UserClient): + """We want check one simple revoke""" + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + data = {'lot': lot['id'], 'file_name': 'test.csv'} + base64_bytes = base64.b64encode(b'This is a test') + base64_string = base64_bytes.decode('utf-8') + data['file'] = base64_string + user.post(res=TradeDocument, data=data) + + # the manager shares the temporary lot with the SCRAP as an incoming lot + # for the CRAP to confirm it + request_post = { + 'type': 'Trade', + 'devices': [], + 'userFrom': user2.email, + 'userTo': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirm': True, } - # check validation error - user2.post(res=models.Action, data=request_confirm_revoke, status=422) + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() - - # The manager add again device_10 - assert len(trade.devices) == 9 - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:]) - - assert device_10.actions[-1].t == 'Confirm' - assert device_10 in trade.devices - assert len(trade.devices) == 10 - - - # the SCRAP confirms the action trade for device_10 - request_reconfirm = { + # the SCRAP confirms 1 in its outgoing lot + request_confirm = { 'type': 'Confirm', 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ] + 'devices': [], + 'documents': [trade.documents[0].id] } - user2.post(res=models.Action, data=request_reconfirm) - assert device_10.actions[-1].t == 'Confirm' - assert device_10.actions[-1].user == trade.user_from - assert device_10.actions[-2].t == 'Confirm' - assert device_10.actions[-2].user == trade.user_to - assert device_10.actions[-3].t == 'ConfirmRevoke' - assert len(device_10.actions) == 13 - """ + user2.post(res=models.Action, data=request_confirm) + + # the SCRAP revoke the document + request_revoke = { + 'type': 'Revoke', + 'action': trade.id, + 'devices': [], + 'documents': [trade.documents[0].id] + } + + user2.post(res=models.Action, data=request_revoke) + + # Manager confirmRevoke + request_revoke = { + 'type': 'ConfirmRevoke', + 'action': trade.id, + 'devices': [], + 'documents': [trade.documents[0].id] + } + + user.post(res=models.Action, data=request_revoke) + assert trade.documents[0].actions[-1].t == 'ConfirmRevoke' + assert trade.documents[0].actions[-2].t == 'Revoke' + user.post(res=models.Action, data=request_revoke, status=422) From 2a66fb94a3825ba639f53ae71ff1b45df3bf555a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 21 May 2021 13:16:30 +0200 Subject: [PATCH 13/31] actions trade for documents --- ereuse_devicehub/resources/action/schemas.py | 149 +++++++++++++++++- .../resources/action/views/trade.py | 42 ++--- .../resources/tradedocument/views.py | 9 ++ 3 files changed, 176 insertions(+), 24 deletions(-) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index fb33cfea..3c99c3ef 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -16,6 +16,7 @@ from ereuse_devicehub.resources import enums from ereuse_devicehub.resources.action import models as m from ereuse_devicehub.resources.agent import schemas as s_agent from ereuse_devicehub.resources.device import schemas as s_device +from ereuse_devicehub.resources.tradedocument import schemas as s_document from ereuse_devicehub.resources.enums import AppearanceRange, BiosAccessRange, FunctionalityRange, \ PhysicalErasureMethod, R_POSITIVE, RatingRange, \ Severity, SnapshotSoftware, TestDataStorageLength @@ -67,6 +68,14 @@ class ActionWithMultipleDevices(Action): collection_class=OrderedSet) +class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): + documents = NestedOn(s_document.TradeDocument, + many=True, + required=False, + only_query='id', + collection_class=OrderedSet) + + class Add(ActionWithOneDevice): __doc__ = m.Add.__doc__ @@ -457,7 +466,7 @@ class CancelReservation(Organize): __doc__ = m.CancelReservation.__doc__ -class Confirm(ActionWithMultipleDevices): +class Confirm(ActionWithMultipleTradeDocuments): __doc__ = m.Confirm.__doc__ action = NestedOn('Action', only_query='id') @@ -469,8 +478,55 @@ class Confirm(ActionWithMultipleDevices): txt = "Device {} not exist in the trade".format(dev.devicehub_id) raise ValidationError(txt) + for doc in data.get('documents', []): + # if document not exist in the Trade, then this query is wrong + if not doc in data['action'].documents: + txt = "Document {} not exist in the trade".format(doc.file_name) + raise ValidationError(txt) -class Revoke(ActionWithMultipleDevices): + @validates_schema + def validate_docs(self, data): + """If there are one device than have one confirmation, + then remove the list this device of the list of devices of this action + """ + if not data['devices'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + actions = copy.copy(doc.actions) + actions.reverse() + for ac in actions: + if ac == data['action']: + # If document have the last action the action Trade + documents.append(doc) + break + + if ac.t == Confirm.t and not ac.user == g.user: + # If document is confirmed but is not for g.user, then need confirm + documents.append(doc) + break + + if ac.t == 'Revoke' and not ac.user == g.user: + # If document is revoke before from other user + # it's not possible confirm now + break + + if ac.t == 'ConfirmRevoke' and ac.user == g.user: + # if the last action is a ConfirmRevoke this mean than not there are + # other confirmation from the real owner of the trade + break + + if ac.t == Confirm.t and ac.user == g.user: + # If dodument is confirmed we don't need confirmed again + break + + if not documents: + txt = 'No there are documents to confirm' + raise ValidationError(txt) + + +class Revoke(ActionWithMultipleTradeDocuments): __doc__ = m.Revoke.__doc__ action = NestedOn('Action', only_query='id') @@ -482,20 +538,103 @@ class Revoke(ActionWithMultipleDevices): txt = "Device {} not exist in the trade".format(dev.devicehub_id) raise ValidationError(txt) + for doc in data.get('documents', []): + # if document not exist in the Trade, then this query is wrong + if not doc in data['action'].documents: + txt = "Document {} not exist in the trade".format(doc.file_name) + raise ValidationError(txt) -class ConfirmRevoke(ActionWithMultipleDevices): + @validates_schema + def validate_documents(self, data): + """Check if there are or no one before confirmation, + This is not checked in the view becouse the list of documents is inmutable + + """ + if not data['devices'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + actions = copy.copy(doc.actions) + actions.reverse() + for ac in actions: + if ac == data['action']: + # data['action'] is a Trade action, if this is the first action + # to find mean that this document don't have a confirmation + break + + if ac.t == 'Revoke' and ac.user == g.user: + # this doc is confirmation jet + break + + if ac.t == Confirm.t and ac.user == g.user: + documents.append(doc) + break + + if not documents: + txt = 'No there are documents to revoke' + raise ValidationError(txt) + + +class ConfirmRevoke(ActionWithMultipleTradeDocuments): __doc__ = m.ConfirmRevoke.__doc__ action = NestedOn('Action', only_query='id') @validates_schema def validate_revoke(self, data: dict): - # import pdb; pdb.set_trace() for dev in data['devices']: # if device not exist in the Trade, then this query is wrong if not dev in data['action'].devices: - txt = "Device {} not exist in the revoke action".format(dev.devicehub_id) + txt = "Device {} not exist in the trade".format(dev.devicehub_id) raise ValidationError(txt) + for doc in data.get('documents', []): + # if document not exist in the Trade, then this query is wrong + if not doc in data['action'].documents: + txt = "Document {} not exist in the trade".format(doc.file_name) + raise ValidationError(txt) + + @validates_schema + def validate_docs(self, data): + """Check if there are or no one before confirmation, + This is not checked in the view becouse the list of documents is inmutable + + """ + if not data['devices'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + actions = copy.copy(doc.actions) + actions.reverse() + for ac in actions: + if ac == data['action']: + # If document have the last action the action for confirm + documents.append(doc) + break + + if ac.t == 'Revoke' and not ac.user == g.user: + # If document is revoke before you can Confirm now + # and revoke is an action of one other user + documents.append(doc) + break + + if ac.t == ConfirmRevoke.t and ac.user == g.user: + # If document is confirmed we don't need confirmed again + break + + if ac.t == Confirm.t: + # if onwer of trade confirm again before than this user Confirm the + # revoke, then is not possible confirm the revoke + # + # If g.user confirm the trade before do a ConfirmRevoke + # then g.user can not to do the ConfirmRevoke more + break + + if not documents: + txt = 'No there are documents with revoke for confirm' + raise ValidationError(txt) + class Trade(ActionWithMultipleDevices): __doc__ = m.Trade.__doc__ diff --git a/ereuse_devicehub/resources/action/views/trade.py b/ereuse_devicehub/resources/action/views/trade.py index 8b22de17..522b323d 100644 --- a/ereuse_devicehub/resources/action/views/trade.py +++ b/ereuse_devicehub/resources/action/views/trade.py @@ -27,7 +27,6 @@ class TradeView(): """ def __init__(self, data, resource_def, schema): - # import pdb; pdb.set_trace() self.schema = schema a = resource_def.schema.load(data) self.trade = Trade(**a) @@ -49,28 +48,34 @@ class TradeView(): # if the confirmation is mandatory, do automatic confirmation only for # owner of the lot if self.trade.confirm: - confirm_devs = Confirm(user=g.user, - action=self.trade, - devices=self.trade.devices) + if self.trade.devices: + confirm_devs = Confirm(user=g.user, + action=self.trade, + devices=self.trade.devices) + db.session.add(confirm_devs) - confirm_docs = Confirm(user=g.user, - action=self.trade, - documents=self.trade.documents) - db.session.add(confirm_devs, confirm_docs) + if self.trade.documents: + confirm_docs = Confirm(user=g.user, + action=self.trade, + documents=self.trade.documents) + db.session.add(confirm_docs) return # check than the user than want to do the action is one of the users # involved in the action assert g.user.id in [self.trade.user_from_id, self.trade.user_to_id] - confirm_from = Confirm(user=self.trade.user_from, - action=self.trade, - devices=self.trade.devices) - confirm_to = Confirm(user=self.trade.user_to, - action=self.trade, - devices=self.trade.devices) - db.session.add(confirm_from) - db.session.add(confirm_to) + if self.trade.user_from == g.user or self.trade.user_from.phantom: + confirm_from = Confirm(user=self.trade.user_from, + action=self.trade, + devices=self.trade.devices) + db.session.add(confirm_from) + + if self.trade.user_to == g.user or self.trade.user_to.phantom: + confirm_to = Confirm(user=self.trade.user_to, + action=self.trade, + devices=self.trade.devices) + db.session.add(confirm_to) def create_phantom_account(self) -> None: """ @@ -141,7 +146,7 @@ class ConfirmMixin(): self.schema = schema a = resource_def.schema.load(data) self.validate(a) - if not a['devices']: + if not a['devices'] and not a['documents']: raise ValidationError('Devices not exist.') self.model = self.Model(**a) @@ -169,7 +174,6 @@ class ConfirmView(ConfirmMixin): """If there are one device than have one confirmation, then remove the list this device of the list of devices of this action """ - # import pdb; pdb.set_trace() real_devices = [] for dev in data['devices']: actions = copy.copy(dev.actions) @@ -181,7 +185,7 @@ class ConfirmView(ConfirmMixin): break if ac.t == Confirm.t and not ac.user == g.user: - # If device is confirmed we don't need confirmed again + # If device is confirmed but is not for g.user, then need confirm real_devices.append(dev) break diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index e85db306..054f29cd 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -6,6 +6,7 @@ from teal.resource import View from ereuse_devicehub.db import db from ereuse_devicehub.resources.tradedocument.models import TradeDocument +from ereuse_devicehub.resources.action.models import Confirm, Revoke from ereuse_devicehub.resources.hash_reports import insert_hash @@ -52,6 +53,14 @@ class TradeDocumentView(View): insert_hash(bfile) doc = TradeDocument(**data) + trade = doc.lot.trade + if trade: + trade.documents.add(doc) + confirm = Confirm(action=trade, + user=g.user, + devices=set(), + documents={doc}) + db.session.add(confirm) db.session.add(doc) db.session().final_flush() ret = self.schema.jsonify(doc) From 589c399231e5783a4a1ce1c572787c34a438a0cb Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 7 Jun 2021 13:10:28 +0200 Subject: [PATCH 14/31] fixing relation files interdependences --- ereuse_devicehub/resources/tradedocument/schemas.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py index 549017c4..e565afc2 100644 --- a/ereuse_devicehub/resources/tradedocument/schemas.py +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -7,7 +7,7 @@ from marshmallow import ValidationError, validates_schema from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.tradedocument import models as m -from ereuse_devicehub.resources.lot import schemas as s_lot +# from ereuse_devicehub.resources.lot import schemas as s_lot class TradeDocument(Thing): @@ -18,7 +18,8 @@ class TradeDocument(Thing): description = SanitizedStr(default='', description=m.TradeDocument.description.comment) file_name = SanitizedStr(default='', description=m.TradeDocument.file_name.comment) file = Raw(type='file') - lot = NestedOn(s_lot.Lot, only_query='id', description=m.TradeDocument.lot.__doc__) + lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__) + # lot = NestedOn(s_lot.Lot, only_query='id', description=m.TradeDocument.lot.__doc__) @validates_schema From b1414cf1435417d73ae0ef80e73846aef4b94045 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 12:38:02 +0200 Subject: [PATCH 15/31] redefine models --- .../resources/tradedocument/models.py | 6 ++-- .../resources/tradedocument/schemas.py | 33 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 409d34eb..7f15f9b1 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -77,8 +77,10 @@ class TradeDocument(Thing): lot.comment = """Lot to which the document is associated""" file_name = Column(db.CIText()) file_name.comment = """This is the name of the file when user up the document.""" - path_name = Column(db.CIText()) - path_name.comment = """This is the name of the file as devicehub save in server.""" + file_hash = Column(db.CIText()) + file_hash.comment = """This is the hash of the file produced from frontend.""" + url = Column(db.CIText()) + url.comment = """This is the url where resides the document.""" __table_args__ = ( db.Index('document_id', id, postgresql_using='hash'), diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py index e565afc2..e63610ec 100644 --- a/ereuse_devicehub/resources/tradedocument/schemas.py +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -1,8 +1,6 @@ -import base64 - -from marshmallow.fields import DateTime, Integer, Raw -from teal.marshmallow import SanitizedStr -from marshmallow import ValidationError, validates_schema +from marshmallow.fields import DateTime, Integer +from teal.marshmallow import SanitizedStr, URL +# from marshmallow import ValidationError, validates_schema from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.resources.schemas import Thing @@ -14,18 +12,17 @@ class TradeDocument(Thing): __doc__ = m.TradeDocument.__doc__ id = Integer(description=m.TradeDocument.id.comment, dump_only=True) date = DateTime(required=False, description=m.TradeDocument.date.comment) - id_document = SanitizedStr(default='', description=m.TradeDocument.id_document.comment) - description = SanitizedStr(default='', description=m.TradeDocument.description.comment) - file_name = SanitizedStr(default='', description=m.TradeDocument.file_name.comment) - file = Raw(type='file') + id_document = SanitizedStr(data_key='documentId', + default='', + description=m.TradeDocument.id_document.comment) + description = SanitizedStr(default='', + description=m.TradeDocument.description.comment) + file_name = SanitizedStr(data_key='filename', + default='', + description=m.TradeDocument.file_name.comment) + file_hash = SanitizedStr(data_key='hash', + default='', + description=m.TradeDocument.file_hash.comment) + url = URL(description=m.TradeDocument.url.comment) lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__) # lot = NestedOn(s_lot.Lot, only_query='id', description=m.TradeDocument.lot.__doc__) - - - @validates_schema - def validate_filestream(self, data): - if not data.get('file'): - txt = 'Error, no there are any file for save' - raise ValidationError(txt) - - data['file'] = base64.b64decode(data['file']) From 315eb097c97c427fe676f8d549c15861f4575282 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 12:38:24 +0200 Subject: [PATCH 16/31] change view for save hash --- .../resources/tradedocument/views.py | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index 054f29cd..dcb1e6e7 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -7,35 +7,7 @@ from teal.resource import View from ereuse_devicehub.db import db from ereuse_devicehub.resources.tradedocument.models import TradeDocument from ereuse_devicehub.resources.action.models import Confirm, Revoke -from ereuse_devicehub.resources.hash_reports import insert_hash - - -def save_doc(data, user): - """ - This function allow save a snapshot in json format un a TMP_SNAPSHOTS directory - The file need to be saved with one name format with the stamptime and uuid joins - """ - filename = data['file_name'] - lot = data['lot'] - now = datetime.now() - year = now.year - month = now.month - day = now.day - hour = now.hour - minutes = now.minute - created = time.time() - - name_file = f"{year}-{month}-{day}-{hour}-{minutes}_{created}_{user}_{filename}" - path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user) - path = os.path.join(path_dir_base, str(lot.id)) - path_name = os.path.join(path, name_file) - - os.system(f'mkdir -p {path}') - - with open(path_name, 'wb') as doc_file: - doc_file.write(data['file']) - - return path_name +from ereuse_devicehub.resources.hash_reports import ReportHash class TradeDocumentView(View): @@ -47,10 +19,11 @@ class TradeDocumentView(View): def post(self): """Add one document.""" + # import pdb; pdb.set_trace() data = request.get_json(validate=True) - data['path_name'] = save_doc(data, g.user.email) - bfile = data.pop('file') - insert_hash(bfile) + hash3 = data['file_hash'] + db_hash = ReportHash(hash3=hash3) + db.session.add(db_hash) doc = TradeDocument(**data) trade = doc.lot.trade From 80b7b98ec8df49ef5da660d3fe69a10e455d686e Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 12:58:10 +0200 Subject: [PATCH 17/31] adding trade documents to migration file --- .../51439cf24be8_change_trade_action.py | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py index 5bf3ef54..4f8c87f0 100644 --- a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py +++ b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py @@ -83,7 +83,7 @@ def upgrade(): schema=f'{get_inv()}' ) - # ## User + ## User op.add_column('user', sa.Column('active', sa.Boolean(), default=True, nullable=True), schema='common') op.add_column('user', sa.Column('phantom', sa.Boolean(), default=False, nullable=True), @@ -95,9 +95,87 @@ def upgrade(): op.alter_column('user', 'phantom', nullable=False, schema='common') + ## TradeDocument + op.create_table('trade_document', + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + comment='The last time Devicehub recorded a change for \n this thing.\n ' + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + comment='When Devicehub created this.' + ), + sa.Column( + 'id', + sa.BigInteger(), + nullable=False, + comment='The identifier of the device for this database. Used only\n internally for software; users should not use this.\n ' + ), + sa.Column( + 'date', + sa.DateTime(), + nullable=True, + comment='The date of document, some documents need to have one date\n ' + ), + sa.Column( + 'id_document', + citext.CIText(), + nullable=True, + comment='The id of one document like invoice so they can be linked.' + ), + sa.Column( + 'description', + citext.CIText(), + nullable=True, + comment='A description of document.' + ), + sa.Column( + 'owner_id', + postgresql.UUID(as_uuid=True), + nullable=False + ), + sa.Column( + 'lot_id', + postgresql.UUID(as_uuid=True), + nullable=False + ), + sa.Column( + 'file_name', + citext.CIText(), + nullable=True, + comment='This is the name of the file when user up the document.' + ), + sa.Column( + 'file_hash', + citext.CIText(), + nullable=True, + comment='This is the hash of the file produced from frontend.' + ), + sa.Column( + 'url', + citext.CIText(), + nullable=True, + comment='This is the url where resides the document.' + ), + sa.ForeignKeyConstraint(['lot_id'], ['lot.id'],), + sa.ForeignKeyConstraint(['owner_id'], ['common.user.id'],), + sa.PrimaryKeyConstraint('id') + ) + op.create_index('document_id', 'trade_document', ['id'], unique=False, postgresql_using='hash') + op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False) + op.create_index(op.f('ix_trade_document_updated'), 'trade_document', ['updated'], unique=False) + + def downgrade(): op.drop_table('confirm', schema=f'{get_inv()}') op.drop_table('trade', schema=f'{get_inv()}') + op.drop_table('trade', schema=f'{get_inv()}') op.create_table('trade', sa.Column('shipping_date', sa.TIMESTAMP(timezone=True), nullable=True, comment='When are the devices going to be ready \n \ From b1f7b629c7e13a161e93bd04fcc5397febb3d269 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 13:11:49 +0200 Subject: [PATCH 18/31] fixing url --- .../51439cf24be8_change_trade_action.py | 6 ++-- .../resources/tradedocument/models.py | 31 ++++++------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py index 4f8c87f0..faa88ab8 100644 --- a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py +++ b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py @@ -5,11 +5,12 @@ Revises: eca457d8b2a4 Create Date: 2021-03-15 17:40:34.410408 """ +import sqlalchemy as sa +import citext +import teal from alembic import op from alembic import context from sqlalchemy.dialects import postgresql -import sqlalchemy as sa -import citext # revision identifiers, used by Alembic. @@ -160,6 +161,7 @@ def upgrade(): sa.Column( 'url', citext.CIText(), + teal.db.URL(), nullable=True, comment='This is the url where resides the document.' ), diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 7f15f9b1..6bce51e7 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -1,28 +1,17 @@ -import os - -from itertools import chain from citext import CIText -from flask import current_app as app, g +from flask import g from sqlalchemy.dialects.postgresql import UUID from ereuse_devicehub.db import db from ereuse_devicehub.resources.user.models import User from sortedcontainers import SortedSet -from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time +from ereuse_devicehub.resources.models import Thing -from sqlalchemy import BigInteger, Boolean, Column, Float, ForeignKey, Integer, \ - Sequence, SmallInteger, Unicode, inspect, text -from sqlalchemy.ext.declarative import declared_attr -from sqlalchemy.orm import ColumnProperty, backref, relationship, validates -from sqlalchemy.util import OrderedSet -from sqlalchemy_utils import ColorType -from teal.db import CASCADE_OWN, CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, \ - check_lower, check_range -from teal.resource import url_for_resource +from sqlalchemy import BigInteger, Column, Sequence +from sqlalchemy.orm import backref +from teal.db import CASCADE_OWN, URL -from ereuse_devicehub.resources.utils import hashcode -from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, ComputerChassis, \ - DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState +from ereuse_devicehub.resources.enums import Severity _sorted_documents = { @@ -37,8 +26,8 @@ class TradeDocument(Thing): and the action trade need to be confirmed for the both users of the trade. This confirmation can be revoked and this revoked need to be ConfirmRevoke for have some efect. - - This documents can be invoices or list of devices or certificates of erasure of + + This documents can be invoices or list of devices or certificates of erasure of one disk. Like a Devices one document have actions and is possible add or delete of one lot @@ -68,7 +57,7 @@ class TradeDocument(Thing): lot_id = db.Column(UUID(as_uuid=True), db.ForeignKey('lot.id'), nullable=False) - lot = db.relationship('Lot', + lot = db.relationship('Lot', backref=backref('documents', lazy=True, cascade=CASCADE_OWN, @@ -79,7 +68,7 @@ class TradeDocument(Thing): file_name.comment = """This is the name of the file when user up the document.""" file_hash = Column(db.CIText()) file_hash.comment = """This is the hash of the file produced from frontend.""" - url = Column(db.CIText()) + url = db.Column(URL()) url.comment = """This is the url where resides the document.""" __table_args__ = ( From 1e05f600a76d52fab22bc2fad08dff933b9e4a17 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 14:29:38 +0200 Subject: [PATCH 19/31] fixing schema in migration file --- .../migrations/versions/51439cf24be8_change_trade_action.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py index faa88ab8..32a05b31 100644 --- a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py +++ b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py @@ -167,7 +167,8 @@ def upgrade(): ), sa.ForeignKeyConstraint(['lot_id'], ['lot.id'],), sa.ForeignKeyConstraint(['owner_id'], ['common.user.id'],), - sa.PrimaryKeyConstraint('id') + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}' ) op.create_index('document_id', 'trade_document', ['id'], unique=False, postgresql_using='hash') op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False) From 814922dbff516654b41e6a6d2cbb09dbd728fe44 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 14:45:41 +0200 Subject: [PATCH 20/31] change tradedocuments in an other file version --- .../versions/3a3601ac8224_tradedocuments.py | 111 ++++++++++++++++++ .../51439cf24be8_change_trade_action.py | 80 ------------- 2 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py diff --git a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py new file mode 100644 index 00000000..18b944b9 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py @@ -0,0 +1,111 @@ +"""tradeDocuments + +Revision ID: 3a3601ac8224 +Revises: 51439cf24be8 +Create Date: 2021-06-15 14:38:59.931818 + +""" +import teal +import citext +import sqlalchemy as sa +from alembic import op +from alembic import context +from sqlalchemy.dialects import postgresql + + + +# revision identifiers, used by Alembic. +revision = '3a3601ac8224' +down_revision = '51439cf24be8' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + +def upgrade(): + op.create_table('trade_document', + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + comment='The last time Devicehub recorded a change for \n this thing.\n ' + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + comment='When Devicehub created this.' + ), + sa.Column( + 'id', + sa.BigInteger(), + nullable=False, + comment='The identifier of the device for this database. Used only\n internally for software; users should not use this.\n ' + ), + sa.Column( + 'date', + sa.DateTime(), + nullable=True, + comment='The date of document, some documents need to have one date\n ' + ), + sa.Column( + 'id_document', + citext.CIText(), + nullable=True, + comment='The id of one document like invoice so they can be linked.' + ), + sa.Column( + 'description', + citext.CIText(), + nullable=True, + comment='A description of document.' + ), + sa.Column( + 'owner_id', + postgresql.UUID(as_uuid=True), + nullable=False + ), + sa.Column( + 'lot_id', + postgresql.UUID(as_uuid=True), + nullable=False + ), + sa.Column( + 'file_name', + citext.CIText(), + nullable=True, + comment='This is the name of the file when user up the document.' + ), + sa.Column( + 'file_hash', + citext.CIText(), + nullable=True, + comment='This is the hash of the file produced from frontend.' + ), + sa.Column( + 'url', + citext.CIText(), + teal.db.URL(), + nullable=True, + comment='This is the url where resides the document.' + ), + sa.ForeignKeyConstraint(['lot_id'], [f'{get_inv()}.lot.id'],), + sa.ForeignKeyConstraint(['owner_id'], ['common.user.id'],), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}' + ) + op.create_index('document_id', 'trade_document', ['id'], unique=False, postgresql_using='hash', schema=f'{get_inv()}') + op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False, schema=f'{get_inv()}') + op.create_index(op.f('ix_trade_document_updated'), 'trade_document', ['updated'], unique=False, schema=f'{get_inv()}') + + +def downgrade(): + op.drop_table('trade_document', schema=f'{get_inv()}') + diff --git a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py index 32a05b31..778f484b 100644 --- a/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py +++ b/ereuse_devicehub/migrations/versions/51439cf24be8_change_trade_action.py @@ -96,89 +96,9 @@ def upgrade(): op.alter_column('user', 'phantom', nullable=False, schema='common') - ## TradeDocument - op.create_table('trade_document', - sa.Column( - 'updated', - sa.TIMESTAMP(timezone=True), - server_default=sa.text('CURRENT_TIMESTAMP'), - nullable=False, - comment='The last time Devicehub recorded a change for \n this thing.\n ' - ), - sa.Column( - 'created', - sa.TIMESTAMP(timezone=True), - server_default=sa.text('CURRENT_TIMESTAMP'), - nullable=False, - comment='When Devicehub created this.' - ), - sa.Column( - 'id', - sa.BigInteger(), - nullable=False, - comment='The identifier of the device for this database. Used only\n internally for software; users should not use this.\n ' - ), - sa.Column( - 'date', - sa.DateTime(), - nullable=True, - comment='The date of document, some documents need to have one date\n ' - ), - sa.Column( - 'id_document', - citext.CIText(), - nullable=True, - comment='The id of one document like invoice so they can be linked.' - ), - sa.Column( - 'description', - citext.CIText(), - nullable=True, - comment='A description of document.' - ), - sa.Column( - 'owner_id', - postgresql.UUID(as_uuid=True), - nullable=False - ), - sa.Column( - 'lot_id', - postgresql.UUID(as_uuid=True), - nullable=False - ), - sa.Column( - 'file_name', - citext.CIText(), - nullable=True, - comment='This is the name of the file when user up the document.' - ), - sa.Column( - 'file_hash', - citext.CIText(), - nullable=True, - comment='This is the hash of the file produced from frontend.' - ), - sa.Column( - 'url', - citext.CIText(), - teal.db.URL(), - nullable=True, - comment='This is the url where resides the document.' - ), - sa.ForeignKeyConstraint(['lot_id'], ['lot.id'],), - sa.ForeignKeyConstraint(['owner_id'], ['common.user.id'],), - sa.PrimaryKeyConstraint('id'), - schema=f'{get_inv()}' - ) - op.create_index('document_id', 'trade_document', ['id'], unique=False, postgresql_using='hash') - op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False) - op.create_index(op.f('ix_trade_document_updated'), 'trade_document', ['updated'], unique=False) - - def downgrade(): op.drop_table('confirm', schema=f'{get_inv()}') op.drop_table('trade', schema=f'{get_inv()}') - op.drop_table('trade', schema=f'{get_inv()}') op.create_table('trade', sa.Column('shipping_date', sa.TIMESTAMP(timezone=True), nullable=True, comment='When are the devices going to be ready \n \ From f7070dbbf7cfdd731799fb0124a9c7783a784fdc Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 15:14:29 +0200 Subject: [PATCH 21/31] adding documents in schema of lots --- ereuse_devicehub/resources/lot/schemas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ereuse_devicehub/resources/lot/schemas.py b/ereuse_devicehub/resources/lot/schemas.py index 2119c048..5dcf9f63 100644 --- a/ereuse_devicehub/resources/lot/schemas.py +++ b/ereuse_devicehub/resources/lot/schemas.py @@ -5,6 +5,7 @@ from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.resources.deliverynote import schemas as s_deliverynote from ereuse_devicehub.resources.device import schemas as s_device from ereuse_devicehub.resources.action import schemas as s_action +from ereuse_devicehub.resources.tradedocument import schemas as s_document from ereuse_devicehub.resources.enums import TransferState from ereuse_devicehub.resources.lot import models as m from ereuse_devicehub.resources.models import STR_SIZE @@ -27,4 +28,5 @@ class Lot(Thing): transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment) receiver_address = SanitizedStr(validate=f.validate.Length(max=42)) deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True) + documents = NestedOn(s_document.TradeDocument, dump_only=True) trade = NestedOn(s_action.Trade, dump_only=True) From 0a96d4f6ef976dc69d450a55569977e07bd50a5b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Jun 2021 15:22:49 +0200 Subject: [PATCH 22/31] fixing schemas interdependences --- ereuse_devicehub/resources/lot/schemas.py | 2 +- ereuse_devicehub/resources/tradedocument/schemas.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ereuse_devicehub/resources/lot/schemas.py b/ereuse_devicehub/resources/lot/schemas.py index 5dcf9f63..5604cc2e 100644 --- a/ereuse_devicehub/resources/lot/schemas.py +++ b/ereuse_devicehub/resources/lot/schemas.py @@ -28,5 +28,5 @@ class Lot(Thing): transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment) receiver_address = SanitizedStr(validate=f.validate.Length(max=42)) deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True) - documents = NestedOn(s_document.TradeDocument, dump_only=True) + documents = NestedOn('TradeDocument', many=True, dump_only=True) trade = NestedOn(s_action.Trade, dump_only=True) diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py index e63610ec..8e96e54a 100644 --- a/ereuse_devicehub/resources/tradedocument/schemas.py +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -25,4 +25,3 @@ class TradeDocument(Thing): description=m.TradeDocument.file_hash.comment) url = URL(description=m.TradeDocument.url.comment) lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__) - # lot = NestedOn(s_lot.Lot, only_query='id', description=m.TradeDocument.lot.__doc__) From 4b7a40e6c1545f273302ffc92ca0f7ad93f87a5e Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 16 Jun 2021 10:53:18 +0200 Subject: [PATCH 23/31] bugfix taiga 2280 --- ereuse_devicehub/resources/action/schemas.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 2a54e789..8a3d7cf4 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -721,11 +721,19 @@ class Trade(ActionWithMultipleDevices): @validates_schema def validate_email_users(self, data: dict): """We need at least one user""" - if not (data['user_from_email'] or data['user_to_email']): + confirm = data['confirm'] + user_from = data['user_from_email'] + user_to = data['user_to_email'] + + if not (user_from or user_to): txt = "you need one user from or user to for to do a trade" raise ValidationError(txt) - if not g.user.email in [data['user_from_email'], data['user_to_email']]: + if confirm and not (user_from and user_to): + txt = "you need one user for to do a trade" + raise ValidationError(txt) + + if not g.user.email in [user_from, user_to]: txt = "you need to be one of participate of the action" raise ValidationError(txt) @@ -740,7 +748,8 @@ class Trade(ActionWithMultipleDevices): txt = "you need a code to be able to do the traceability" raise ValidationError(txt) - data['code'] = data['code'].replace('@', '_') + if not data['confirm']: + data['code'] = data['code'].replace('@', '_') class InitTransfer(Trade): From f85a0f5ef4ec221756d4905c18eba13092e6dbfc Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 22 Jun 2021 16:53:10 +0200 Subject: [PATCH 24/31] fixing documents trade --- .../versions/3a3601ac8224_tradedocuments.py | 10 +++++++ ereuse_devicehub/resources/device/schemas.py | 2 +- .../resources/tradedocument/models.py | 27 +++++++++++++++++++ .../resources/tradedocument/schemas.py | 12 ++++++--- .../resources/tradedocument/views.py | 1 - 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py index 18b944b9..a574047c 100644 --- a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py +++ b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py @@ -101,6 +101,16 @@ def upgrade(): sa.PrimaryKeyConstraint('id'), schema=f'{get_inv()}' ) + # Action document table + op.create_table('action_trade_document', + sa.Column('document_id', sa.BigInteger(), nullable=False), + sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint(['action_id'], [f'{get_inv()}.action.id'], ), + sa.ForeignKeyConstraint(['document_id'], [f'{get_inv()}.trade_document.id'], ), + sa.PrimaryKeyConstraint('document_id', 'action_id'), + schema=f'{get_inv()}' + ) + op.create_index('document_id', 'trade_document', ['id'], unique=False, postgresql_using='hash', schema=f'{get_inv()}') op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False, schema=f'{get_inv()}') op.create_index(op.f('ix_trade_document_updated'), 'trade_document', ['updated'], unique=False, schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index 828c9827..d9a658fe 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -50,7 +50,7 @@ class Device(Thing): description='The lots where this device is directly under.') rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__) price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__) - trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__) + # trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__) trading = SanitizedStr(dump_only=True, description='') physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__) traking= EnumField(states.Traking, dump_only=True, description=m.Device.physical.__doc__) diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 6bce51e7..e8aa4637 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -1,3 +1,4 @@ +import copy from citext import CIText from flask import g @@ -88,6 +89,32 @@ class TradeDocument(Thing): """ return sorted(self.actions_multiple_docs, key=lambda x: x.created) + @property + def trading(self): + """The trading state, or None if no Trade action has + ever been performed to this device. This extract the posibilities for to do""" + + confirm = 'Confirm' + to_confirm = 'ToConfirm' + revoke = 'Revoke' + + if not self.actions: + return + + actions = copy.copy(self.actions) + actions = list(reversed(actions)) + ac = actions[0] + + if ac.type == confirm: + return confirm + + if ac.type == revoke: + return revoke + + if ac.type == confirm: + if ac.user == self.owner: + return confirm + return to_confirm def last_action_of(self, *types): """Gets the last action of the given types. diff --git a/ereuse_devicehub/resources/tradedocument/schemas.py b/ereuse_devicehub/resources/tradedocument/schemas.py index 8e96e54a..fab95dc4 100644 --- a/ereuse_devicehub/resources/tradedocument/schemas.py +++ b/ereuse_devicehub/resources/tradedocument/schemas.py @@ -1,4 +1,4 @@ -from marshmallow.fields import DateTime, Integer +from marshmallow.fields import DateTime, Integer, validate from teal.marshmallow import SanitizedStr, URL # from marshmallow import ValidationError, validates_schema @@ -16,12 +16,16 @@ class TradeDocument(Thing): default='', description=m.TradeDocument.id_document.comment) description = SanitizedStr(default='', - description=m.TradeDocument.description.comment) + description=m.TradeDocument.description.comment, + validate=validate.Length(max=500)) file_name = SanitizedStr(data_key='filename', default='', - description=m.TradeDocument.file_name.comment) + description=m.TradeDocument.file_name.comment, + validate=validate.Length(max=100)) file_hash = SanitizedStr(data_key='hash', default='', - description=m.TradeDocument.file_hash.comment) + description=m.TradeDocument.file_hash.comment, + validate=validate.Length(max=64)) url = URL(description=m.TradeDocument.url.comment) lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__) + trading = SanitizedStr(dump_only=True, description='') diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index dcb1e6e7..fa66b481 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -19,7 +19,6 @@ class TradeDocumentView(View): def post(self): """Add one document.""" - # import pdb; pdb.set_trace() data = request.get_json(validate=True) hash3 = data['file_hash'] db_hash = ReportHash(hash3=hash3) From 77000882f53512e9b12f92390e516bcefee91554 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 24 Jun 2021 11:41:14 +0200 Subject: [PATCH 25/31] change model action_trade_documents --- .../versions/3a3601ac8224_tradedocuments.py | 1 + ereuse_devicehub/resources/action/models.py | 36 ++++- ereuse_devicehub/resources/action/schemas.py | 124 ++++++++++++++++-- .../resources/tradedocument/models.py | 2 +- 4 files changed, 147 insertions(+), 16 deletions(-) diff --git a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py index a574047c..5656595e 100644 --- a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py +++ b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py @@ -117,5 +117,6 @@ def upgrade(): def downgrade(): + op.drop_table('action_trade_document', schema=f'{get_inv()}') op.drop_table('trade_document', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 3d12bfda..60c6f91f 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -296,9 +296,9 @@ class ActionDevice(db.Model): primary_key=True) -class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): +class ActionWithMultipleTradeDocuments(Action): documents = relationship(TradeDocument, - backref=backref('actions_multiple_docs', lazy=True, **_sorted_actions), + backref=backref('actions_docs', lazy=True, **_sorted_actions), secondary=lambda: ActionTradeDocument.__table__, order_by=lambda: TradeDocument.id, collection_class=OrderedSet) @@ -1447,8 +1447,36 @@ class Reserve(Organize): class CancelReservation(Organize): """The act of cancelling a reservation.""" +class ConfirmDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments): + """Users confirm the one action trade this confirmation it's link to trade + and the document that confirm + """ + user_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), + nullable=False, + default=lambda: g.user.id) + user = db.relationship(User, primaryjoin=user_id == User.id) + user_comment = """The user that accept the offer.""" + action_id = db.Column(UUID(as_uuid=True), + db.ForeignKey('action.id'), + nullable=False) + action = db.relationship('Action', + backref=backref('acceptances_document', + uselist=True, + lazy=True, + order_by=lambda: Action.end_time, + collection_class=list), + primaryjoin='Confirm.action_id == Action.id') -class Confirm(JoinedTableMixin, ActionWithMultipleTradeDocuments): + def __repr__(self) -> str: + if self.action.t in ['Trade']: + origin = 'To' + if self.user == self.action.user_from: + origin = 'From' + return '<{0.t} {0.id} accepted by {1}>'.format(self, origin) + + +class Confirm(JoinedTableMixin, ActionWithMultipleDevices): """Users confirm the one action trade this confirmation it's link to trade and the devices that confirm """ @@ -1488,7 +1516,7 @@ class ConfirmRevoke(Confirm): return '<{0.t} {0.id} accepted by {0.user}>'.format(self) -class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments): +class Trade(JoinedTableMixin, ActionWithMultipleDevices): """Trade actions log the political exchange of devices between users. Every time a trade action is performed, the old user looses its political possession, for example ownership, in favor of another diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 8a3d7cf4..7d935750 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -55,6 +55,11 @@ class Action(Thing): class ActionWithOneDevice(Action): + __doc__ = m.ActionWithOneDevice.__doc__ + doc = NestedOn(s_document.TradeDocument, only_query='id') + + +class ActionWithOneDocument(Action): __doc__ = m.ActionWithOneDevice.__doc__ device = NestedOn(s_device.Device, only_query='id') @@ -68,14 +73,6 @@ class ActionWithMultipleDevices(Action): collection_class=OrderedSet) -class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): - documents = NestedOn(s_document.TradeDocument, - many=True, - required=False, - only_query='id', - collection_class=OrderedSet) - - class Add(ActionWithOneDevice): __doc__ = m.Add.__doc__ @@ -466,7 +463,7 @@ class CancelReservation(Organize): __doc__ = m.CancelReservation.__doc__ -class Confirm(ActionWithMultipleTradeDocuments): +class Confirm(ActionWithMultipleDevices): __doc__ = m.Confirm.__doc__ action = NestedOn('Action', only_query='id') @@ -478,6 +475,13 @@ class Confirm(ActionWithMultipleTradeDocuments): txt = "Device {} not exist in the trade".format(dev.devicehub_id) raise ValidationError(txt) + +class ConfirmDocument(ActionWithOneDocument): + __doc__ = m.Confirm.__doc__ + action = NestedOn('Action', only_query='id') + + @validates_schema + def validate_revoke(self, data: dict): for doc in data.get('documents', []): # if document not exist in the Trade, then this query is wrong if not doc in data['action'].documents: @@ -526,7 +530,7 @@ class Confirm(ActionWithMultipleTradeDocuments): raise ValidationError(txt) -class Revoke(ActionWithMultipleTradeDocuments): +class Revoke(ActionWithMultipleDevices): __doc__ = m.Revoke.__doc__ action = NestedOn('Action', only_query='id') @@ -576,7 +580,51 @@ class Revoke(ActionWithMultipleTradeDocuments): raise ValidationError(txt) -class ConfirmRevoke(ActionWithMultipleTradeDocuments): +class RevokeDocument(ActionWithOneDocument): + __doc__ = m.Revoke.__doc__ + action = NestedOn('Action', only_query='id') + + @validates_schema + def validate_revoke(self, data: dict): + for doc in data.get('documents', []): + # if document not exist in the Trade, then this query is wrong + if not doc in data['action'].documents: + txt = "Document {} not exist in the trade".format(doc.file_name) + raise ValidationError(txt) + + @validates_schema + def validate_documents(self, data): + """Check if there are or no one before confirmation, + This is not checked in the view becouse the list of documents is inmutable + + """ + if not data['devices'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + actions = copy.copy(doc.actions) + actions.reverse() + for ac in actions: + if ac == data['action']: + # data['action'] is a Trade action, if this is the first action + # to find mean that this document don't have a confirmation + break + + if ac.t == 'Revoke' and ac.user == g.user: + # this doc is confirmation jet + break + + if ac.t == Confirm.t and ac.user == g.user: + documents.append(doc) + break + + if not documents: + txt = 'No there are documents to revoke' + raise ValidationError(txt) + + +class ConfirmRevoke(ActionWithMultipleDevices): __doc__ = m.ConfirmRevoke.__doc__ action = NestedOn('Action', only_query='id') @@ -636,6 +684,60 @@ class ConfirmRevoke(ActionWithMultipleTradeDocuments): raise ValidationError(txt) +class ConfirmRevokeDocument(ActionWithOneDocument): + __doc__ = m.ConfirmRevoke.__doc__ + action = NestedOn('Action', only_query='id') + + @validates_schema + def validate_revoke(self, data: dict): + for doc in data.get('documents', []): + # if document not exist in the Trade, then this query is wrong + if not doc in data['action'].documents: + txt = "Document {} not exist in the trade".format(doc.file_name) + raise ValidationError(txt) + + @validates_schema + def validate_docs(self, data): + """Check if there are or no one before confirmation, + This is not checked in the view becouse the list of documents is inmutable + + """ + if not data['devices'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + actions = copy.copy(doc.actions) + actions.reverse() + for ac in actions: + if ac == data['action']: + # If document have the last action the action for confirm + documents.append(doc) + break + + if ac.t == 'Revoke' and not ac.user == g.user: + # If document is revoke before you can Confirm now + # and revoke is an action of one other user + documents.append(doc) + break + + if ac.t == ConfirmRevoke.t and ac.user == g.user: + # If document is confirmed we don't need confirmed again + break + + if ac.t == Confirm.t: + # if onwer of trade confirm again before than this user Confirm the + # revoke, then is not possible confirm the revoke + # + # If g.user confirm the trade before do a ConfirmRevoke + # then g.user can not to do the ConfirmRevoke more + break + + if not documents: + txt = 'No there are documents with revoke for confirm' + raise ValidationError(txt) + + class Trade(ActionWithMultipleDevices): __doc__ = m.Trade.__doc__ date = DateTime(data_key='date', required=False) diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index e8aa4637..5b0939c8 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -87,7 +87,7 @@ class TradeDocument(Thing): Actions are returned by descending ``created`` time. """ - return sorted(self.actions_multiple_docs, key=lambda x: x.created) + return sorted(self.actions_docs, key=lambda x: x.created) @property def trading(self): From 8a797f079b36049ae65e20612bf39c90b98d8808 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 24 Jun 2021 19:09:12 +0200 Subject: [PATCH 26/31] fixing models and build views for revokeDocument --- .../versions/3a3601ac8224_tradedocuments.py | 12 ++ ereuse_devicehub/resources/action/__init__.py | 13 ++ ereuse_devicehub/resources/action/models.py | 12 +- ereuse_devicehub/resources/action/schemas.py | 25 ++-- .../resources/action/views/trade.py | 136 +++++++++++++++++- ereuse_devicehub/resources/device/models.py | 10 +- .../resources/tradedocument/models.py | 12 +- .../resources/tradedocument/views.py | 4 +- 8 files changed, 194 insertions(+), 30 deletions(-) diff --git a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py index 5656595e..19e65087 100644 --- a/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py +++ b/ereuse_devicehub/migrations/versions/3a3601ac8224_tradedocuments.py @@ -115,8 +115,20 @@ def upgrade(): op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False, schema=f'{get_inv()}') op.create_index(op.f('ix_trade_document_updated'), 'trade_document', ['updated'], unique=False, schema=f'{get_inv()}') + op.create_table('confirm_document', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False), + + sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ), + sa.ForeignKeyConstraint(['action_id'], [f'{get_inv()}.action.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['common.user.id'], ), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}' + ) def downgrade(): op.drop_table('action_trade_document', schema=f'{get_inv()}') + op.drop_table('confirm_document', schema=f'{get_inv()}') op.drop_table('trade_document', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/__init__.py b/ereuse_devicehub/resources/action/__init__.py index 368b4528..ef19f907 100644 --- a/ereuse_devicehub/resources/action/__init__.py +++ b/ereuse_devicehub/resources/action/__init__.py @@ -270,6 +270,19 @@ class TradeDef(ActionDef): SCHEMA = schemas.Trade +class ConfirmDocumentDef(ActionDef): + VIEW = None + SCHEMA = schemas.ConfirmDocument + + +class RevokeDocumentDef(ActionDef): + VIEW = None + SCHEMA = schemas.RevokeDocument + + + + + class CancelTradeDef(ActionDef): VIEW = None SCHEMA = schemas.CancelTrade diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 60c6f91f..debe4a55 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -296,7 +296,7 @@ class ActionDevice(db.Model): primary_key=True) -class ActionWithMultipleTradeDocuments(Action): +class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): documents = relationship(TradeDocument, backref=backref('actions_docs', lazy=True, **_sorted_actions), secondary=lambda: ActionTradeDocument.__table__, @@ -1466,14 +1466,18 @@ class ConfirmDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments): lazy=True, order_by=lambda: Action.end_time, collection_class=list), - primaryjoin='Confirm.action_id == Action.id') + primaryjoin='ConfirmDocument.action_id == Action.id') def __repr__(self) -> str: if self.action.t in ['Trade']: origin = 'To' if self.user == self.action.user_from: origin = 'From' - return '<{0.t} {0.id} accepted by {1}>'.format(self, origin) + return '<{0.t}app/views/inventory/ {0.id} accepted by {1}>'.format(self, origin) + + +class RevokeDocument(ConfirmDocument): + pass class Confirm(JoinedTableMixin, ActionWithMultipleDevices): @@ -1516,7 +1520,7 @@ class ConfirmRevoke(Confirm): return '<{0.t} {0.id} accepted by {0.user}>'.format(self) -class Trade(JoinedTableMixin, ActionWithMultipleDevices): +class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments): """Trade actions log the political exchange of devices between users. Every time a trade action is performed, the old user looses its political possession, for example ownership, in favor of another diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 7d935750..89ca4c07 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -55,15 +55,19 @@ class Action(Thing): class ActionWithOneDevice(Action): - __doc__ = m.ActionWithOneDevice.__doc__ - doc = NestedOn(s_document.TradeDocument, only_query='id') - - -class ActionWithOneDocument(Action): __doc__ = m.ActionWithOneDevice.__doc__ device = NestedOn(s_device.Device, only_query='id') +class ActionWithMultipleDocuments(Action): + __doc__ = m.ActionWithMultipleTradeDocuments.__doc__ + documents = NestedOn(s_document.TradeDocument, + many=True, + required=True, # todo test ensuring len(devices) >= 1 + only_query='id', + collection_class=OrderedSet) + + class ActionWithMultipleDevices(Action): __doc__ = m.ActionWithMultipleDevices.__doc__ devices = NestedOn(s_device.Device, @@ -476,7 +480,7 @@ class Confirm(ActionWithMultipleDevices): raise ValidationError(txt) -class ConfirmDocument(ActionWithOneDocument): +class ConfirmDocument(ActionWithMultipleDocuments): __doc__ = m.Confirm.__doc__ action = NestedOn('Action', only_query='id') @@ -580,8 +584,8 @@ class Revoke(ActionWithMultipleDevices): raise ValidationError(txt) -class RevokeDocument(ActionWithOneDocument): - __doc__ = m.Revoke.__doc__ +class RevokeDocument(ActionWithMultipleDocuments): + __doc__ = m.RevokeDocument.__doc__ action = NestedOn('Action', only_query='id') @validates_schema @@ -598,7 +602,8 @@ class RevokeDocument(ActionWithOneDocument): This is not checked in the view becouse the list of documents is inmutable """ - if not data['devices'] == OrderedSet(): + import pdb; pdb.set_trace() + if not data['documents'] == OrderedSet(): return documents = [] @@ -684,7 +689,7 @@ class ConfirmRevoke(ActionWithMultipleDevices): raise ValidationError(txt) -class ConfirmRevokeDocument(ActionWithOneDocument): +class ConfirmRevokeDocument(ActionWithMultipleDocuments): __doc__ = m.ConfirmRevoke.__doc__ action = NestedOn('Action', only_query='id') diff --git a/ereuse_devicehub/resources/action/views/trade.py b/ereuse_devicehub/resources/action/views/trade.py index 1a2eb4d1..6e9d71f5 100644 --- a/ereuse_devicehub/resources/action/views/trade.py +++ b/ereuse_devicehub/resources/action/views/trade.py @@ -5,7 +5,8 @@ from sqlalchemy.util import OrderedSet from teal.marshmallow import ValidationError from ereuse_devicehub.db import db -from ereuse_devicehub.resources.action.models import Trade, Confirm, ConfirmRevoke, Revoke +from ereuse_devicehub.resources.action.models import (Trade, Confirm, ConfirmRevoke, + Revoke, RevokeDocument, ConfirmDocument) from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.lot.views import delete_from_trade @@ -152,7 +153,7 @@ class ConfirmMixin(): self.schema = schema a = resource_def.schema.load(data) self.validate(a) - if not a['devices'] and not a['documents']: + if not a['devices']: raise ValidationError('Devices not exist.') self.model = self.Model(**a) @@ -267,3 +268,134 @@ class ConfirmRevokeView(ConfirmMixin): dev.reset_owner() trade.lot.devices.difference_update(devices) + + +class ConfirmDocumentMixin(): + """ + Very Important: + ============== + All of this Views than inherit of this class is executed for users + than is not owner of the Trade action. + + The owner of Trade action executed this actions of confirm and revoke from the + lot + + """ + + Model = None + + def __init__(self, data, resource_def, schema): + # import pdb; pdb.set_trace() + self.schema = schema + a = resource_def.schema.load(data) + self.validate(a) + if not a['documents']: + raise ValidationError('Documents not exist.') + self.model = self.Model(**a) + + def post(self): + db.session().final_flush() + ret = self.schema.jsonify(self.model) + ret.status_code = 201 + db.session.commit() + return ret + + +class ConfirmDocumentView(ConfirmDocumentMixin): + """Handler for manager the Confirmation register from post + + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [device_id] + } + """ + + Model = Confirm + + def validate(self, data): + """If there are one device than have one confirmation, + then remove the list this device of the list of devices of this action + """ + real_devices = [] + for dev in data['devices']: + ac = dev.last_action_trading + if ac.type == Confirm.t and not ac.user == g.user: + real_devices.append(dev) + + data['devices'] = OrderedSet(real_devices) + + # Change the owner for every devices + for dev in data['devices']: + user_to = data['action'].user_to + dev.change_owner(user_to) + + +class RevokeDocumentView(ConfirmDocumentMixin): + """Handler for manager the Revoke register from post + + request_revoke = { + 'type': 'Revoke', + 'action': trade.id, + 'devices': [device_id], + } + + """ + + Model = RevokeDocument + + def __init__(self, data, resource_def, schema): + self.schema = schema + a = resource_def.schema.load(data) + self.validate(a) + + def validate(self, data): + """All devices need to have the status of DoubleConfirmation.""" + + ### check ### + if not data['documents']: + raise ValidationError('Documents not exist.') + + for doc in data['documents']: + if not doc.trading == 'Confirmed': + txt = 'Some of documents do not have enough to confirm for to do a revoke' + ValidationError(txt) + ### End check ### + + +class ConfirmRevokeDocumentView(ConfirmDocumentMixin): + """Handler for manager the Confirmation register from post + + request_confirm_revoke = { + 'type': 'ConfirmRevoke', + 'action': action_revoke.id, + 'devices': [device_id] + } + + """ + + Model = ConfirmRevoke + + def validate(self, data): + """All devices need to have the status of revoke.""" + + if not data['action'].type == 'Revoke': + txt = 'Error: this action is not a revoke action' + ValidationError(txt) + + for dev in data['devices']: + if not dev.trading == 'Revoke': + txt = 'Some of devices do not have revoke to confirm' + ValidationError(txt) + + devices = OrderedSet(data['devices']) + data['devices'] = devices + + # Change the owner for every devices + # data['action'] == 'Revoke' + + trade = data['action'].action + for dev in devices: + dev.reset_owner() + + trade.lot.devices.difference_update(devices) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 61363ed8..d586a102 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -625,11 +625,11 @@ class Computer(Device): It is a subset of the Linux definition of DMI / DMI decode. """ amount = Column(Integer, check_range('amount', min=0, max=100), default=0) - owner_id = db.Column(UUID(as_uuid=True), - db.ForeignKey(User.id), - nullable=False, - default=lambda: g.user.id) - author = db.relationship(User, primaryjoin=owner_id == User.id) + # owner_id = db.Column(UUID(as_uuid=True), + # db.ForeignKey(User.id), + # nullable=False, + # default=lambda: g.user.id) + # author = db.relationship(User, primaryjoin=owner_id == User.id) transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False) transfer_state.comment = TransferState.__doc__ receiver_id = db.Column(UUID(as_uuid=True), diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 5b0939c8..b38e7374 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -94,8 +94,9 @@ class TradeDocument(Thing): """The trading state, or None if no Trade action has ever been performed to this device. This extract the posibilities for to do""" - confirm = 'Confirm' - to_confirm = 'ToConfirm' + # import pdb; pdb.set_trace() + confirm = 'ConfirmDocument' + to_confirm = 'To Confirm' revoke = 'Revoke' if not self.actions: @@ -105,16 +106,13 @@ class TradeDocument(Thing): actions = list(reversed(actions)) ac = actions[0] - if ac.type == confirm: - return confirm - if ac.type == revoke: return revoke if ac.type == confirm: if ac.user == self.owner: - return confirm - return to_confirm + return to_confirm + return 'Confirmed' def last_action_of(self, *types): """Gets the last action of the given types. diff --git a/ereuse_devicehub/resources/tradedocument/views.py b/ereuse_devicehub/resources/tradedocument/views.py index fa66b481..4222eceb 100644 --- a/ereuse_devicehub/resources/tradedocument/views.py +++ b/ereuse_devicehub/resources/tradedocument/views.py @@ -6,7 +6,7 @@ from teal.resource import View from ereuse_devicehub.db import db from ereuse_devicehub.resources.tradedocument.models import TradeDocument -from ereuse_devicehub.resources.action.models import Confirm, Revoke +from ereuse_devicehub.resources.action.models import ConfirmDocument from ereuse_devicehub.resources.hash_reports import ReportHash @@ -28,7 +28,7 @@ class TradeDocumentView(View): trade = doc.lot.trade if trade: trade.documents.add(doc) - confirm = Confirm(action=trade, + confirm = ConfirmDocument(action=trade, user=g.user, devices=set(), documents={doc}) From c8b0c56bbb64dd628868e33e6a3bf4adcb3df040 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 28 Jun 2021 16:06:07 +0200 Subject: [PATCH 27/31] confirm Revoke, confirm and revoke documents actions --- ereuse_devicehub/resources/action/__init__.py | 4 +- ereuse_devicehub/resources/action/models.py | 5 + ereuse_devicehub/resources/action/schemas.py | 210 +++++++----------- .../resources/action/views/trade.py | 60 ++--- .../resources/action/views/views.py | 13 ++ .../resources/tradedocument/models.py | 55 ++--- 6 files changed, 147 insertions(+), 200 deletions(-) diff --git a/ereuse_devicehub/resources/action/__init__.py b/ereuse_devicehub/resources/action/__init__.py index ef19f907..5e581cd5 100644 --- a/ereuse_devicehub/resources/action/__init__.py +++ b/ereuse_devicehub/resources/action/__init__.py @@ -280,7 +280,9 @@ class RevokeDocumentDef(ActionDef): SCHEMA = schemas.RevokeDocument - +class ConfirmRevokeDocumentDef(ActionDef): + VIEW = None + SCHEMA = schemas.ConfirmRevokeDocument class CancelTradeDef(ActionDef): diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index debe4a55..d8bcb3ce 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -1447,6 +1447,7 @@ class Reserve(Organize): class CancelReservation(Organize): """The act of cancelling a reservation.""" + class ConfirmDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments): """Users confirm the one action trade this confirmation it's link to trade and the document that confirm @@ -1480,6 +1481,10 @@ class RevokeDocument(ConfirmDocument): pass +class ConfirmRevokeDocument(ConfirmDocument): + pass + + class Confirm(JoinedTableMixin, ActionWithMultipleDevices): """Users confirm the one action trade this confirmation it's link to trade and the devices that confirm diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 89ca4c07..a0a3a45f 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -480,60 +480,6 @@ class Confirm(ActionWithMultipleDevices): raise ValidationError(txt) -class ConfirmDocument(ActionWithMultipleDocuments): - __doc__ = m.Confirm.__doc__ - action = NestedOn('Action', only_query='id') - - @validates_schema - def validate_revoke(self, data: dict): - for doc in data.get('documents', []): - # if document not exist in the Trade, then this query is wrong - if not doc in data['action'].documents: - txt = "Document {} not exist in the trade".format(doc.file_name) - raise ValidationError(txt) - - @validates_schema - def validate_docs(self, data): - """If there are one device than have one confirmation, - then remove the list this device of the list of devices of this action - """ - if not data['devices'] == OrderedSet(): - return - - documents = [] - for doc in data['documents']: - actions = copy.copy(doc.actions) - actions.reverse() - for ac in actions: - if ac == data['action']: - # If document have the last action the action Trade - documents.append(doc) - break - - if ac.t == Confirm.t and not ac.user == g.user: - # If document is confirmed but is not for g.user, then need confirm - documents.append(doc) - break - - if ac.t == 'Revoke' and not ac.user == g.user: - # If document is revoke before from other user - # it's not possible confirm now - break - - if ac.t == 'ConfirmRevoke' and ac.user == g.user: - # if the last action is a ConfirmRevoke this mean than not there are - # other confirmation from the real owner of the trade - break - - if ac.t == Confirm.t and ac.user == g.user: - # If dodument is confirmed we don't need confirmed again - break - - if not documents: - txt = 'No there are documents to confirm' - raise ValidationError(txt) - - class Revoke(ActionWithMultipleDevices): __doc__ = m.Revoke.__doc__ action = NestedOn('Action', only_query='id') @@ -584,17 +530,43 @@ class Revoke(ActionWithMultipleDevices): raise ValidationError(txt) -class RevokeDocument(ActionWithMultipleDocuments): - __doc__ = m.RevokeDocument.__doc__ +class ConfirmDocument(ActionWithMultipleDocuments): + __doc__ = m.Confirm.__doc__ action = NestedOn('Action', only_query='id') @validates_schema - def validate_revoke(self, data: dict): - for doc in data.get('documents', []): - # if document not exist in the Trade, then this query is wrong - if not doc in data['action'].documents: - txt = "Document {} not exist in the trade".format(doc.file_name) - raise ValidationError(txt) + def validate_documents(self, data): + """If there are one device than have one confirmation, + then remove the list this device of the list of devices of this action + """ + # import pdb; pdb.set_trace() + if data['documents'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + if not doc.lot.trade: + return + + data['action'] = doc.lot.trade + + if not doc.actions: + continue + + ac = doc.actions[-1] + + if ac.t == 'ConfirmDocument' and not ac.user == g.user: + # If document is confirmed but is not for g.user, then need confirm + documents.append(doc) + + if not documents: + txt = 'No there are documents to confirm' + raise ValidationError(txt) + + +class RevokeDocument(ActionWithMultipleDocuments): + __doc__ = m.RevokeDocument.__doc__ + action = NestedOn('Action', only_query='id') @validates_schema def validate_documents(self, data): @@ -602,33 +574,63 @@ class RevokeDocument(ActionWithMultipleDocuments): This is not checked in the view becouse the list of documents is inmutable """ - import pdb; pdb.set_trace() - if not data['documents'] == OrderedSet(): + if data['documents'] == OrderedSet(): return documents = [] for doc in data['documents']: - actions = copy.copy(doc.actions) - actions.reverse() - for ac in actions: - if ac == data['action']: - # data['action'] is a Trade action, if this is the first action - # to find mean that this document don't have a confirmation - break + if not doc.lot.trade: + return - if ac.t == 'Revoke' and ac.user == g.user: - # this doc is confirmation jet - break + data['action'] = doc.lot.trade - if ac.t == Confirm.t and ac.user == g.user: - documents.append(doc) - break + if not doc.actions: + continue + + ac = doc.actions[-1] + + if ac.t == 'ConfirmDocument' and ac.user == g.user: + documents.append(doc) if not documents: txt = 'No there are documents to revoke' raise ValidationError(txt) +class ConfirmRevokeDocument(ActionWithMultipleDocuments): + __doc__ = m.ConfirmRevoke.__doc__ + action = NestedOn('Action', only_query='id') + + @validates_schema + def validate_documents(self, data): + """Check if there are or no one before confirmation, + This is not checked in the view becouse the list of documents is inmutable + + """ + if data['documents'] == OrderedSet(): + return + + documents = [] + for doc in data['documents']: + if not doc.lot.trade: + return + + if not doc.actions: + continue + + ac = doc.actions[-1] + + if ac.t == 'RevokeDocument' and not ac.user == g.user: + # If document is revoke before you can Confirm now + # and revoke is an action of one other user + data['action'] = ac + documents.append(doc) + + if not documents: + txt = 'No there are documents with revoke for confirm' + raise ValidationError(txt) + + class ConfirmRevoke(ActionWithMultipleDevices): __doc__ = m.ConfirmRevoke.__doc__ action = NestedOn('Action', only_query='id') @@ -689,60 +691,6 @@ class ConfirmRevoke(ActionWithMultipleDevices): raise ValidationError(txt) -class ConfirmRevokeDocument(ActionWithMultipleDocuments): - __doc__ = m.ConfirmRevoke.__doc__ - action = NestedOn('Action', only_query='id') - - @validates_schema - def validate_revoke(self, data: dict): - for doc in data.get('documents', []): - # if document not exist in the Trade, then this query is wrong - if not doc in data['action'].documents: - txt = "Document {} not exist in the trade".format(doc.file_name) - raise ValidationError(txt) - - @validates_schema - def validate_docs(self, data): - """Check if there are or no one before confirmation, - This is not checked in the view becouse the list of documents is inmutable - - """ - if not data['devices'] == OrderedSet(): - return - - documents = [] - for doc in data['documents']: - actions = copy.copy(doc.actions) - actions.reverse() - for ac in actions: - if ac == data['action']: - # If document have the last action the action for confirm - documents.append(doc) - break - - if ac.t == 'Revoke' and not ac.user == g.user: - # If document is revoke before you can Confirm now - # and revoke is an action of one other user - documents.append(doc) - break - - if ac.t == ConfirmRevoke.t and ac.user == g.user: - # If document is confirmed we don't need confirmed again - break - - if ac.t == Confirm.t: - # if onwer of trade confirm again before than this user Confirm the - # revoke, then is not possible confirm the revoke - # - # If g.user confirm the trade before do a ConfirmRevoke - # then g.user can not to do the ConfirmRevoke more - break - - if not documents: - txt = 'No there are documents with revoke for confirm' - raise ValidationError(txt) - - class Trade(ActionWithMultipleDevices): __doc__ = m.Trade.__doc__ date = DateTime(data_key='date', required=False) diff --git a/ereuse_devicehub/resources/action/views/trade.py b/ereuse_devicehub/resources/action/views/trade.py index 6e9d71f5..5779127a 100644 --- a/ereuse_devicehub/resources/action/views/trade.py +++ b/ereuse_devicehub/resources/action/views/trade.py @@ -1,12 +1,11 @@ -import copy - from flask import g from sqlalchemy.util import OrderedSet from teal.marshmallow import ValidationError from ereuse_devicehub.db import db from ereuse_devicehub.resources.action.models import (Trade, Confirm, ConfirmRevoke, - Revoke, RevokeDocument, ConfirmDocument) + Revoke, RevokeDocument, ConfirmDocument, + ConfirmRevokeDocument) from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.lot.views import delete_from_trade @@ -307,28 +306,22 @@ class ConfirmDocumentView(ConfirmDocumentMixin): request_confirm = { 'type': 'Confirm', 'action': trade.id, - 'devices': [device_id] + 'documents': [document_id], } """ - Model = Confirm + Model = ConfirmDocument def validate(self, data): """If there are one device than have one confirmation, then remove the list this device of the list of devices of this action """ - real_devices = [] - for dev in data['devices']: - ac = dev.last_action_trading - if ac.type == Confirm.t and not ac.user == g.user: - real_devices.append(dev) - - data['devices'] = OrderedSet(real_devices) - - # Change the owner for every devices - for dev in data['devices']: - user_to = data['action'].user_to - dev.change_owner(user_to) + for doc in data['documents']: + ac = doc.trading + if not doc.trading in ['Confirm', 'Need Confirmation']: + txt = 'Some of documents do not have enough to confirm for to do a Doble Confirmation' + ValidationError(txt) + ### End check ### class RevokeDocumentView(ConfirmDocumentMixin): @@ -337,18 +330,13 @@ class RevokeDocumentView(ConfirmDocumentMixin): request_revoke = { 'type': 'Revoke', 'action': trade.id, - 'devices': [device_id], + 'documents': [document_id], } """ Model = RevokeDocument - def __init__(self, data, resource_def, schema): - self.schema = schema - a = resource_def.schema.load(data) - self.validate(a) - def validate(self, data): """All devices need to have the status of DoubleConfirmation.""" @@ -357,7 +345,7 @@ class RevokeDocumentView(ConfirmDocumentMixin): raise ValidationError('Documents not exist.') for doc in data['documents']: - if not doc.trading == 'Confirmed': + if not doc.trading in ['Document Confirmed', 'Confirm']: txt = 'Some of documents do not have enough to confirm for to do a revoke' ValidationError(txt) ### End check ### @@ -369,33 +357,21 @@ class ConfirmRevokeDocumentView(ConfirmDocumentMixin): request_confirm_revoke = { 'type': 'ConfirmRevoke', 'action': action_revoke.id, - 'devices': [device_id] + 'documents': [document_id], } """ - Model = ConfirmRevoke + Model = ConfirmRevokeDocument def validate(self, data): """All devices need to have the status of revoke.""" - if not data['action'].type == 'Revoke': + if not data['action'].type == 'RevokeDocument': txt = 'Error: this action is not a revoke action' ValidationError(txt) - for dev in data['devices']: - if not dev.trading == 'Revoke': - txt = 'Some of devices do not have revoke to confirm' + for doc in data['documents']: + if not doc.trading == 'Revoke': + txt = 'Some of documents do not have revoke to confirm' ValidationError(txt) - - devices = OrderedSet(data['devices']) - data['devices'] = devices - - # Change the owner for every devices - # data['action'] == 'Revoke' - - trade = data['action'].action - for dev in devices: - dev.reset_owner() - - trade.lot.devices.difference_update(devices) diff --git a/ereuse_devicehub/resources/action/views/views.py b/ereuse_devicehub/resources/action/views/views.py index 8cb324ff..fbb15ea6 100644 --- a/ereuse_devicehub/resources/action/views/views.py +++ b/ereuse_devicehub/resources/action/views/views.py @@ -202,6 +202,19 @@ class ActionView(View): confirm_revoke = trade_view.ConfirmRevokeView(json, resource_def, self.schema) return confirm_revoke.post() + if json['type'] == 'RevokeDocument': + revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema) + return revoke.post() + + if json['type'] == 'ConfirmDocument': + confirm = trade_view.ConfirmDocumentView(json, resource_def, self.schema) + return confirm.post() + + if json['type'] == 'ConfirmRevokeDocument': + confirm_revoke = trade_view.ConfirmRevokeDocumentView(json, resource_def, self.schema) + return confirm_revoke.post() + + # import pdb; pdb.set_trace() a = resource_def.schema.load(json) Model = db.Model._decl_class_registry.data[json['type']]() action = Model(**a) diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index b38e7374..9c743244 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -94,38 +94,41 @@ class TradeDocument(Thing): """The trading state, or None if no Trade action has ever been performed to this device. This extract the posibilities for to do""" - # import pdb; pdb.set_trace() - confirm = 'ConfirmDocument' - to_confirm = 'To Confirm' + confirm = 'Confirm' + need_confirm = 'Need Confirmation' + double_confirm = 'Document Confirmed' revoke = 'Revoke' - + revoke_pending = 'Revoke Pending' + confirm_revoke = 'Document Revoked' + if not self.actions: - return + return - actions = copy.copy(self.actions) - actions = list(reversed(actions)) - ac = actions[0] + ac = self.actions[-1] - if ac.type == revoke: - return revoke + if ac.type == 'ConfirmRevokeDocument': + # can to do revoke_confirmed + return confirm_revoke - if ac.type == confirm: + if ac.type == 'RevokeDocument': + if ac.user == g.user: + # can todo revoke_pending + return revoke_pending + else: + # can to do confirm_revoke + return revoke + + if ac.type == 'ConfirmDocument': if ac.user == self.owner: - return to_confirm - return 'Confirmed' - - def last_action_of(self, *types): - """Gets the last action of the given types. - - :raise LookupError: Device has not an action of the given type. - """ - try: - # noinspection PyTypeHints - actions = self.actions - actions.sort(key=lambda x: x.created) - return next(e for e in reversed(actions) if isinstance(e, types)) - except StopIteration: - raise LookupError('{!r} does not contain actions of types {}.'.format(self, types)) + if self.owner == g.user: + # can to do revoke + return confirm + else: + # can to do confirm + return need_confirm + else: + # can to do revoke + return double_confirm def _warning_actions(self, actions): return sorted(ev for ev in actions if ev.severity >= Severity.Warning) From b7e652906e7a0d47df2142a96c3b31421f8691e1 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 29 Jun 2021 19:13:00 +0200 Subject: [PATCH 28/31] clean views of trade documents --- ereuse_devicehub/resources/action/schemas.py | 40 +++++-------------- .../resources/action/views/trade.py | 2 +- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index a0a3a45f..37b0a8a0 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -543,7 +543,6 @@ class ConfirmDocument(ActionWithMultipleDocuments): if data['documents'] == OrderedSet(): return - documents = [] for doc in data['documents']: if not doc.lot.trade: return @@ -553,15 +552,9 @@ class ConfirmDocument(ActionWithMultipleDocuments): if not doc.actions: continue - ac = doc.actions[-1] - - if ac.t == 'ConfirmDocument' and not ac.user == g.user: - # If document is confirmed but is not for g.user, then need confirm - documents.append(doc) - - if not documents: - txt = 'No there are documents to confirm' - raise ValidationError(txt) + if not doc.trading == 'Need Confirmation': + txt = 'No there are documents to confirm' + raise ValidationError(txt) class RevokeDocument(ActionWithMultipleDocuments): @@ -574,10 +567,10 @@ class RevokeDocument(ActionWithMultipleDocuments): This is not checked in the view becouse the list of documents is inmutable """ + # import pdb; pdb.set_trace() if data['documents'] == OrderedSet(): return - documents = [] for doc in data['documents']: if not doc.lot.trade: return @@ -587,14 +580,9 @@ class RevokeDocument(ActionWithMultipleDocuments): if not doc.actions: continue - ac = doc.actions[-1] - - if ac.t == 'ConfirmDocument' and ac.user == g.user: - documents.append(doc) - - if not documents: - txt = 'No there are documents to revoke' - raise ValidationError(txt) + if not doc.trading in ['Document Confirmed', 'Confirm']: + txt = 'No there are documents to revoke' + raise ValidationError(txt) class ConfirmRevokeDocument(ActionWithMultipleDocuments): @@ -610,7 +598,6 @@ class ConfirmRevokeDocument(ActionWithMultipleDocuments): if data['documents'] == OrderedSet(): return - documents = [] for doc in data['documents']: if not doc.lot.trade: return @@ -618,17 +605,12 @@ class ConfirmRevokeDocument(ActionWithMultipleDocuments): if not doc.actions: continue - ac = doc.actions[-1] - if ac.t == 'RevokeDocument' and not ac.user == g.user: - # If document is revoke before you can Confirm now - # and revoke is an action of one other user - data['action'] = ac - documents.append(doc) + if not doc.trading == 'Revoke': + txt = 'No there are documents with revoke for confirm' + raise ValidationError(txt) - if not documents: - txt = 'No there are documents with revoke for confirm' - raise ValidationError(txt) + data['action'] = doc.actions[-1] class ConfirmRevoke(ActionWithMultipleDevices): diff --git a/ereuse_devicehub/resources/action/views/trade.py b/ereuse_devicehub/resources/action/views/trade.py index 5779127a..ef7c4efe 100644 --- a/ereuse_devicehub/resources/action/views/trade.py +++ b/ereuse_devicehub/resources/action/views/trade.py @@ -58,7 +58,7 @@ class TradeView(): db.session.add(confirm_devs) if self.trade.documents: - confirm_docs = Confirm(user=g.user, + confirm_docs = ConfirmDocument(user=g.user, action=self.trade, documents=self.trade.documents) db.session.add(confirm_docs) From c5234e0fd4347cad1bc6ea46520bc85312265f21 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 1 Jul 2021 12:07:52 +0200 Subject: [PATCH 29/31] fixing tests --- tests/test_action.py | 33 --------------------------------- tests/test_basic.py | 2 +- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/tests/test_action.py b/tests/test_action.py index 52b793dd..4b5640cb 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -788,7 +788,6 @@ def test_offer_without_to(user: UserClient): 'userFromEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': False, 'code': 'MAX' @@ -816,7 +815,6 @@ def test_offer_without_to(user: UserClient): 'userFromEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': False, 'code': 'MAX' @@ -839,7 +837,6 @@ def test_offer_without_to(user: UserClient): 'userFromEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot2.id, 'confirms': False, 'code': 'MAX' @@ -870,7 +867,6 @@ def test_offer_without_from(user: UserClient, user2: UserClient): 'userToEmail': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot.id, 'confirms': False, 'code': 'MAX' @@ -915,7 +911,6 @@ def test_offer_without_users(user: UserClient): 'devices': [device.id], 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot.id, 'confirms': False, 'code': 'MAX' @@ -949,7 +944,6 @@ def test_offer(user: UserClient): 'userToEmail': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot.id, 'confirms': True, } @@ -976,7 +970,6 @@ def test_offer_without_devices(user: UserClient): 'userToEmail': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -985,7 +978,6 @@ def test_offer_without_devices(user: UserClient): # no there are transfer of devices ->>>>>>> feature/endpoint-confirm @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_price_custom(): @@ -1055,7 +1047,6 @@ def test_endpoint_confirm(user: UserClient, user2: UserClient): 'userToEmail': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1096,7 +1087,6 @@ def test_confirm_revoke(user: UserClient, user2: UserClient): 'userToEmail': user2.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1171,7 +1161,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1361,7 +1350,6 @@ def test_confirmRevoke(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1476,7 +1464,6 @@ def test_trade_case1(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1537,7 +1524,6 @@ def test_trade_case2(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1564,12 +1550,6 @@ def test_trade_case2(user: UserClient, user2: UserClient): 'action': trade.id, 'devices': [device1.id, device2.id], } - user2.post(res=models.Action, data=request_reconfirm) - assert device_10.actions[-1].t == 'Confirm' - assert device_10.actions[-1].user == trade.user_from - assert device_10.actions[-2].t == 'Confirm' - assert device_10.actions[-2].user == trade.user_to - assert device_10.actions[-3].t == 'Revoke' # Normal revoke user.post(res=models.Action, data=request_revoke) @@ -1608,7 +1588,6 @@ def test_trade_case3(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1666,7 +1645,6 @@ def test_trade_case4(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1732,7 +1710,6 @@ def test_trade_case5(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1798,7 +1775,6 @@ def test_trade_case6(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1866,7 +1842,6 @@ def test_trade_case7(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -1932,7 +1907,6 @@ def test_trade_case8(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2005,7 +1979,6 @@ def test_trade_case9(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2086,7 +2059,6 @@ def test_trade_case10(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2171,7 +2143,6 @@ def test_trade_case11(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2240,7 +2211,6 @@ def test_trade_case12(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2315,7 +2285,6 @@ def test_trade_case13(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2390,7 +2359,6 @@ def test_trade_case14(user: UserClient, user2: UserClient): 'userToEmail': user.email, 'price': 10, 'date': "2020-12-01T02:00:00+00:00", - 'documentID': '1', 'lot': lot['id'], 'confirms': True, } @@ -2442,4 +2410,3 @@ def test_trade_case14(user: UserClient, user2: UserClient): assert device.actions[-4].user == trade.user_from assert device.actions[-5].t == 'Trade' assert device.actions[-5].author == trade.user_to ->>>>>>> feature/endpoint-confirm diff --git a/tests/test_basic.py b/tests/test_basic.py index 010a4d42..4cdce061 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -122,4 +122,4 @@ def test_api_docs(client: Client): 'scheme': 'basic', 'name': 'Authorization' } - assert len(docs['definitions']) == 122 + assert len(docs['definitions']) == 125 From 5e8c4a3231b61872c4b24485acc6fa5f63122f45 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 1 Jul 2021 12:08:18 +0200 Subject: [PATCH 30/31] add code of user for show in angular --- ereuse_devicehub/resources/user/models.py | 7 +++++++ ereuse_devicehub/resources/user/schemas.py | 1 + 2 files changed, 8 insertions(+) diff --git a/ereuse_devicehub/resources/user/models.py b/ereuse_devicehub/resources/user/models.py index f1690156..79525286 100644 --- a/ereuse_devicehub/resources/user/models.py +++ b/ereuse_devicehub/resources/user/models.py @@ -59,6 +59,13 @@ class User(Thing): """The individual associated for this database, or None.""" return next(iter(self.individuals), None) + @property + def code(self): + """Code of phantoms accounts""" + if not self.phantom: + return + return self.email.split('@')[0].split('_')[1] + class UserInventory(db.Model): """Relationship between users and their inventories.""" diff --git a/ereuse_devicehub/resources/user/schemas.py b/ereuse_devicehub/resources/user/schemas.py index 220e193b..a70c0da8 100644 --- a/ereuse_devicehub/resources/user/schemas.py +++ b/ereuse_devicehub/resources/user/schemas.py @@ -23,6 +23,7 @@ class User(Thing): description='Use this token in an Authorization header to access the app.' 'The token can change overtime.') inventories = NestedOn(Inventory, many=True, dump_only=True) + code = String(dump_only=True, description='Code of inactive accounts') def __init__(self, only=None, From 8cc535273aedfcf5b264ce2b559b0e18e184d1e5 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 1 Jul 2021 12:16:49 +0200 Subject: [PATCH 31/31] drop old trade tests --- tests/test_trade.py | 920 -------------------------------------------- 1 file changed, 920 deletions(-) delete mode 100644 tests/test_trade.py diff --git a/tests/test_trade.py b/tests/test_trade.py deleted file mode 100644 index f2fc2b07..00000000 --- a/tests/test_trade.py +++ /dev/null @@ -1,920 +0,0 @@ -import os -import base64 -import ipaddress -import json -import shutil -import copy -import pytest - -from datetime import datetime, timedelta -from dateutil.tz import tzutc -from decimal import Decimal -from typing import Tuple, Type -from pytest import raises -from json.decoder import JSONDecodeError - -from flask import current_app as app, g -from sqlalchemy.util import OrderedSet -from teal.enums import Currency, Subdivision - -from ereuse_devicehub.db import db -from ereuse_devicehub.client import UserClient, Client -from ereuse_devicehub.devicehub import Devicehub -from ereuse_devicehub.resources import enums -from ereuse_devicehub.resources.hash_reports import ReportHash -from ereuse_devicehub.resources.user.models import User -from ereuse_devicehub.resources.agent.models import Person -from ereuse_devicehub.resources.lot.models import Lot -from ereuse_devicehub.resources.action import models -from ereuse_devicehub.resources.device import states -from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \ - RamModule, SolidStateDrive -from ereuse_devicehub.resources.enums import ComputerChassis, Severity, TestDataStorageLength -from ereuse_devicehub.resources.tradedocument.models import TradeDocument - -from tests import conftest -from tests.conftest import create_user, file - - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_to(user: UserClient): - """Test one offer with automatic confirmation and without user to""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - device = Device.query.filter_by(id=snapshot['device']['id']).one() - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=[('id', device.id)]) - - # check the owner of the device - assert device.owner.email == user.email - for c in device.components: - assert c.owner.email == user.email - - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'userFrom': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': False, - 'code': 'MAX' - } - user.post(res=models.Action, data=request_post) - - trade = models.Trade.query.one() - assert device in trade.devices - # assert trade.confirm_transfer - 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 trade.user_to in users - assert trade.user_from in users - assert device.owner.email != user.email - for c in device.components: - assert c.owner.email != user.email - - # check if the user_from is owner of the devices - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'userFrom': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': False, - 'code': 'MAX' - } - user.post(res=models.Action, data=request_post, status=422) - trade = models.Trade.query.one() - - # Check if the new phantom account is reused and not duplicated - computer = file('1-device-with-components.snapshot') - snapshot2, _ = user.post(computer, res=models.Snapshot) - device2 = Device.query.filter_by(id=snapshot2['device']['id']).one() - lot2 = Lot('MyLot2') - lot2.owner_id = user.user['id'] - lot2.devices.add(device2) - db.session.add(lot2) - db.session.flush() - request_post2 = { - 'type': 'Trade', - 'devices': [device2.id], - 'userFrom': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot2.id, - 'confirm': False, - 'code': 'MAX' - } - user.post(res=models.Action, data=request_post2) - assert User.query.filter_by(email=device.owner.email).count() == 1 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_from(user: UserClient, user2: UserClient): - """Test one offer without confirmation and without user from""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - lot = Lot('MyLot') - lot.owner_id = user.user['id'] - device = Device.query.filter_by(id=snapshot['device']['id']).one() - - # check the owner of the device - assert device.owner.email == user.email - assert device.owner.email != user2.email - - lot.devices.add(device) - db.session.add(lot) - db.session.flush() - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot.id, - 'confirm': False, - 'code': 'MAX' - } - action, _ = user2.post(res=models.Action, data=request_post, status=422) - - request_post['userTo'] = user.email - action, _ = user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - 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 trade.confirm_transfer - - users = [ac.user for ac in trade.acceptances] - assert trade.user_to in users - assert trade.user_from in users - assert user.email in trade.devices[0].owner.email - assert device.owner.email != user2.email - assert device.owner.email == user.email - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_users(user: UserClient): - """Test one offer with doble confirmation""" - user2 = User(email='baz@baz.cxm', password='baz') - user2.individuals.add(Person(name='Tommy')) - db.session.add(user2) - db.session.commit() - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - lot = Lot('MyLot') - lot.owner_id = user.user['id'] - device = Device.query.filter_by(id=snapshot['device']['id']).one() - lot.devices.add(device) - db.session.add(lot) - db.session.flush() - request_post = { - 'type': 'Trade', - 'devices': [device.id], - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot.id, - 'confirm': False, - 'code': 'MAX' - } - action, response = user.post(res=models.Action, data=request_post, status=422) - txt = 'you need one user from or user to for to do a offer' - assert txt in action['message']['_schema'] - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer(user: UserClient): - """Test one offer with doble confirmation""" - user2 = User(email='baz@baz.cxm', password='baz') - user2.individuals.add(Person(name='Tommy')) - db.session.add(user2) - db.session.commit() - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - lot = Lot('MyLot') - lot.owner_id = user.user['id'] - device = Device.query.filter_by(id=snapshot['device']['id']).one() - assert device.owner.email == user.email - assert device.owner.email != user2.email - lot.devices.add(device) - db.session.add(lot) - db.session.flush() - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot.id, - 'confirm': True, - } - - action, _ = user.post(res=models.Action, data=request_post) - # no there are transfer of devices - assert device.owner.email == user.email - assert device.owner.email != user2.email - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_offer_without_devices(user: UserClient): - """Test one offer with doble confirmation""" - user2 = User(email='baz@baz.cxm', password='baz') - user2.individuals.add(Person(name='Tommy')) - db.session.add(user2) - db.session.commit() - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - # no there are transfer of devices - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_endpoint_confirm(user: UserClient, user2: UserClient): - """Check the normal creation and visualization of one confirmation trade""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - device_id = snapshot['device']['id'] - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=[('id', device_id)]) - - request_post = { - 'type': 'Trade', - 'devices': [device_id], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - assert trade.devices[0].owner.email == user.email - - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [device_id], - 'documents': [] - } - - user2.post(res=models.Action, data=request_confirm) - user2.post(res=models.Action, data=request_confirm, status=422) - assert len(trade.acceptances) == 2 - assert trade.devices[0].owner.email == user2.email - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_confirm_revoke(user: UserClient, user2: UserClient): - """Check the normal revoke of one confirmation""" - snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - device_id = snapshot['device']['id'] - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=[('id', device_id)]) - - request_post = { - 'type': 'Trade', - 'devices': [device_id], - 'userFrom': user.email, - 'userTo': user2.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [device_id], - 'documents': [] - } - - request_revoke = { - 'type': 'Revoke', - 'action': trade.id, - 'devices': [device_id], - 'documents': [] - } - - - # Normal confirmation - user2.post(res=models.Action, data=request_confirm) - - # Normal revoke - user2.post(res=models.Action, data=request_revoke) - - # Error for try duplicate revoke - user2.post(res=models.Action, data=request_revoke, status=422) - assert len(trade.acceptances) == 3 - - # You can not to do one confirmation next of one revoke - user2.post(res=models.Action, data=request_confirm, status=422) - assert len(trade.acceptances) == 3 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_usecase_confirmation(user: UserClient, user2: UserClient): - """Example of one usecase about confirmation""" - # the pRp (manatest_usecase_confirmationger) creates a temporary lot - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - # The manager add 7 device into the lot - snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - snap2, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) - snap3, _ = user.post(file('asus-1001pxd.snapshot'), res=models.Snapshot) - snap4, _ = user.post(file('desktop-9644w8n-lenovo-0169622.snapshot'), res=models.Snapshot) - snap5, _ = user.post(file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot'), res=models.Snapshot) - snap6, _ = user.post(file('1-device-with-components.snapshot'), res=models.Snapshot) - snap7, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=models.Snapshot) - snap8, _ = user.post(file('complete.export.snapshot'), res=models.Snapshot) - snap9, _ = user.post(file('real-hp-quad-core.snapshot.11'), res=models.Snapshot) - snap10, _ = user.post(file('david.lshw.snapshot'), res=models.Snapshot) - - devices = [('id', snap1['device']['id']), - ('id', snap2['device']['id']), - ('id', snap3['device']['id']), - ('id', snap4['device']['id']), - ('id', snap5['device']['id']), - ('id', snap6['device']['id']), - ('id', snap7['device']['id']), - ('id', snap8['device']['id']), - ('id', snap9['device']['id']), - ('id', snap10['device']['id']), - ] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[:7]) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - assert trade.devices[0].actions[-2].t == 'Trade' - assert trade.devices[0].actions[-1].t == 'Confirm' - assert trade.devices[0].actions[-1].user == trade.user_to - - # the SCRAP confirms 3 of the 10 devices in its outgoing lot - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [snap1['device']['id'], snap2['device']['id'], snap3['device']['id']], - 'documents': [] - } - user2.post(res=models.Action, data=request_confirm) - - assert trade.devices[0].actions[-1].t == 'Confirm' - assert trade.devices[0].actions[-1].user == trade.user_from - n_actions = len(trade.devices[0].actions) - - # check validation error - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ], - 'documents': [] - } - - user2.post(res=models.Action, data=request_confirm, status=422) - - - # The manager add 3 device more into the lot - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[7:]) - - assert trade.devices[-1].actions[-2].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_to - assert len(trade.devices[0].actions) == n_actions - - - # the SCRAP confirms the rest of devices - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap1['device']['id'], - snap2['device']['id'], - snap3['device']['id'], - snap4['device']['id'], - snap5['device']['id'], - snap6['device']['id'], - snap7['device']['id'], - snap8['device']['id'], - snap9['device']['id'], - snap10['device']['id'] - ] - } - - user2.post(res=models.Action, data=request_confirm) - assert trade.devices[-1].actions[-3].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_from - assert len(trade.devices[0].actions) == n_actions - - # The manager remove one device of the lot and automaticaly - # is create one revoke action - device_10 = trade.devices[-1] - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - assert len(trade.lot.devices) == len(trade.devices) == 9 - assert not device_10 in trade.devices - assert device_10.actions[-1].t == 'Revoke' - - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - - assert device_10.actions[-1].t == 'Revoke' - assert device_10.actions[-2].t == 'Confirm' - - # the SCRAP confirms the revoke action - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, - 'devices': [ - snap10['device']['id'] - ], - 'documents': [] - } - - user2.post(res=models.Action, data=request_confirm_revoke) - assert device_10.actions[-1].t == 'ConfirmRevoke' - assert device_10.actions[-2].t == 'Revoke' - - request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, - 'devices': [ - snap9['device']['id'] - ], - 'documents': [] - } - - # check validation error - user2.post(res=models.Action, data=request_confirm_revoke, status=422) - - - # The manager add again device_10 - assert len(trade.devices) == 9 - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:]) - - assert device_10.actions[-1].t == 'Confirm' - assert device_10 in trade.devices - assert len(trade.devices) == 10 - - - # the SCRAP confirms the action trade for device_10 - request_reconfirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ], - 'documents': [] - } - user2.post(res=models.Action, data=request_reconfirm) - assert device_10.actions[-1].t == 'Confirm' - assert device_10.actions[-1].user == trade.user_from - assert device_10.actions[-2].t == 'Confirm' - assert device_10.actions[-2].user == trade.user_to - assert device_10.actions[-3].t == 'ConfirmRevoke' - assert len(device_10.actions) == 13 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_confirmRevoke(user: UserClient, user2: UserClient): - """Example of one usecase about confirmation""" - # the pRp (manatest_usecase_confirmationger) creates a temporary lot - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - # The manager add 7 device into the lot - snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - snap2, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) - snap3, _ = user.post(file('asus-1001pxd.snapshot'), res=models.Snapshot) - snap4, _ = user.post(file('desktop-9644w8n-lenovo-0169622.snapshot'), res=models.Snapshot) - snap5, _ = user.post(file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot'), res=models.Snapshot) - snap6, _ = user.post(file('1-device-with-components.snapshot'), res=models.Snapshot) - snap7, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=models.Snapshot) - snap8, _ = user.post(file('complete.export.snapshot'), res=models.Snapshot) - snap9, _ = user.post(file('real-hp-quad-core.snapshot.11'), res=models.Snapshot) - snap10, _ = user.post(file('david.lshw.snapshot'), res=models.Snapshot) - - devices = [('id', snap1['device']['id']), - ('id', snap2['device']['id']), - ('id', snap3['device']['id']), - ('id', snap4['device']['id']), - ('id', snap5['device']['id']), - ('id', snap6['device']['id']), - ('id', snap7['device']['id']), - ('id', snap8['device']['id']), - ('id', snap9['device']['id']), - ('id', snap10['device']['id']), - ] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - # the SCRAP confirms all of devices - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap1['device']['id'], - snap2['device']['id'], - snap3['device']['id'], - snap4['device']['id'], - snap5['device']['id'], - snap6['device']['id'], - snap7['device']['id'], - snap8['device']['id'], - snap9['device']['id'], - snap10['device']['id'] - ] - } - - user2.post(res=models.Action, data=request_confirm) - assert trade.devices[-1].actions[-3].t == 'Trade' - assert trade.devices[-1].actions[-1].t == 'Confirm' - assert trade.devices[-1].actions[-1].user == trade.user_from - - # The manager remove one device of the lot and automaticaly - # is create one revoke action - device_10 = trade.devices[-1] - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - assert len(trade.lot.devices) == len(trade.devices) == 9 - assert not device_10 in trade.devices - assert device_10.actions[-1].t == 'Revoke' - - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:], status=200) - - assert device_10.actions[-1].t == 'Revoke' - assert device_10.actions[-2].t == 'Confirm' - - # The manager add again device_10 - assert len(trade.devices) == 9 - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:]) - - assert device_10.actions[-1].t == 'Confirm' - 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'] - ], - 'documents': [] - } - - # check validation error - user2.post(res=models.Action, data=request_confirm_revoke, status=422) - - # the SCRAP confirms the action trade for device_10 - request_reconfirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [ - snap10['device']['id'] - ], - 'documents': [] - } - user2.post(res=models.Action, data=request_reconfirm) - assert device_10.actions[-1].t == 'Confirm' - assert device_10.actions[-1].user == trade.user_from - assert device_10.actions[-2].t == 'Confirm' - assert device_10.actions[-2].user == trade.user_to - assert device_10.actions[-3].t == 'Revoke' - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_simple_add_document(user: UserClient): - """Example of one document inserted into one lot""" - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - doc = TradeDocument(**{'file_name': 'test', - 'owner_id': user.user['id'], - 'lot_id': lot['id']}) - db.session.add(doc) - db.session.flush() - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_add_document_to_lot(user: UserClient, user2: UserClient, client: Client, app: Devicehub): - """Example of one document inserted into one lot""" - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - data = {'lot': lot['id'], 'file_name': 'test.csv'} - base64_bytes = base64.b64encode(b'This is a test') - base64_string = base64_bytes.decode('utf-8') - data['file'] = base64_string - doc, _ = user.post(res=TradeDocument, - data=data) - - assert len(ReportHash.query.all()) == 1 - - path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'] , user.email) - path = os.path.join(path_dir_base, lot['id']) - assert len(os.listdir(path)) == 1 - - user.get(res=TradeDocument, item=doc['id']) - user.delete(res=TradeDocument, item=doc['id']) - - # check permitions - doc, _ = user.post(res=TradeDocument, data=data) - - # anonyms users - client.get(res=TradeDocument, item=doc['id'], status=401) - client.delete(res=TradeDocument, item=doc['id'], status=401) - - # other user - user2.get(res=TradeDocument, item=doc['id'], status=404) - user2.delete(res=TradeDocument, item=doc['id'], status=404) - shutil.rmtree(path) - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_usecase1_document(user: UserClient, user2: UserClient): - """Example of one usecase about confirmation""" - # the pRp (manatest_usecase_confirmationger) creates a temporary lot - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - data = {'lot': lot['id'], 'file_name': 'test.csv'} - base64_bytes = base64.b64encode(b'This is a test') - base64_string = base64_bytes.decode('utf-8') - data['file'] = base64_string - user.post(res=TradeDocument, data=data) - user.post(res=TradeDocument, data=data) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - assert trade.documents[0].actions[-2].t == 'Trade' - assert trade.documents[0].actions[-1].t == 'Confirm' - assert trade.documents[0].actions[-1].user == trade.user_to - assert trade.documents[1].actions[-2].t == 'Trade' - assert trade.documents[1].actions[-1].t == 'Confirm' - assert trade.documents[1].actions[-1].user == trade.user_to - assert trade.documents[0].path_name != trade.documents[1].path_name - - # the SCRAP confirms 1 of the 2 documents in its outgoing lot - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id] - } - - user2.post(res=models.Action, data=request_confirm) - assert trade.documents[0].actions[-1].t == 'Confirm' - assert trade.documents[0].actions[-1].user == trade.user_from - n_actions = len(trade.documents[0].actions) - - # check validation error - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id], - } - - user2.post(res=models.Action, data=request_confirm, status=422) - - # Both users can up de documents - user2.post(res=TradeDocument, data=data) - assert len(trade.documents) == 3 - - # user1 confirm document2 - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[2].id], - } - - assert trade.documents[2].actions[-1].user == trade.user_from - assert trade.documents[2].actions[-1].t == 'Confirm' - - user.post(res=models.Action, data=request_confirm) - assert trade.documents[2].actions[-1].user == trade.user_to - assert trade.documents[2].actions[-1].t == 'Confirm' - - # clean docs of test - path_dir_base = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'], user.email) - path = os.path.join(path_dir_base, lot['id']) - - path_dir_base_from = os.path.join(app.config['PATH_DOCUMENTS_STORAGE'], user2.email) - path_from = os.path.join(path_dir_base_from, lot['id']) - - shutil.rmtree(path) - shutil.rmtree(path_from) - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_document_confirm_revoke(user: UserClient, user2: UserClient): - """We want check one simple revoke""" - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - data = {'lot': lot['id'], 'file_name': 'test.csv'} - base64_bytes = base64.b64encode(b'This is a test') - base64_string = base64_bytes.decode('utf-8') - data['file'] = base64_string - user.post(res=TradeDocument, data=data) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - # the SCRAP confirms 1 in its outgoing lot - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id] - } - - user2.post(res=models.Action, data=request_confirm) - - # the SCRAP revoke the document - request_revoke = { - 'type': 'Revoke', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id] - } - - user2.post(res=models.Action, data=request_revoke) - assert trade.documents[0].actions[-1].t == 'Revoke' - assert trade.documents[0].actions[-2].t == 'Confirm' - user2.post(res=models.Action, data=request_revoke, status=422) - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_document_confirm_revoke_confirmRevoke(user: UserClient, user2: UserClient): - """We want check one simple revoke""" - lot, _ = user.post({'name': 'MyLot'}, res=Lot) - data = {'lot': lot['id'], 'file_name': 'test.csv'} - base64_bytes = base64.b64encode(b'This is a test') - base64_string = base64_bytes.decode('utf-8') - data['file'] = base64_string - user.post(res=TradeDocument, data=data) - - # the manager shares the temporary lot with the SCRAP as an incoming lot - # for the CRAP to confirm it - request_post = { - 'type': 'Trade', - 'devices': [], - 'userFrom': user2.email, - 'userTo': user.email, - 'price': 10, - 'date': "2020-12-01T02:00:00+00:00", - 'lot': lot['id'], - 'confirm': True, - } - - user.post(res=models.Action, data=request_post) - trade = models.Trade.query.one() - - # the SCRAP confirms 1 in its outgoing lot - request_confirm = { - 'type': 'Confirm', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id] - } - - user2.post(res=models.Action, data=request_confirm) - - # the SCRAP revoke the document - request_revoke = { - 'type': 'Revoke', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id] - } - - user2.post(res=models.Action, data=request_revoke) - - # Manager confirmRevoke - request_revoke = { - 'type': 'ConfirmRevoke', - 'action': trade.id, - 'devices': [], - 'documents': [trade.documents[0].id] - } - - user.post(res=models.Action, data=request_revoke) - assert trade.documents[0].actions[-1].t == 'ConfirmRevoke' - assert trade.documents[0].actions[-2].t == 'Revoke' - user.post(res=models.Action, data=request_revoke, status=422)