diff --git a/CHANGELOG.md b/CHANGELOG.md index e443e88a..e8bb5e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,36 +14,38 @@ ml). ## [1.0.10-beta] - [addend] #170 can delete/deactivate devices. - [bugfix] #168 can to do a trade without devices. -- [addend] #167 new actions of status devices: use, recycling, refurbish and management. +- [added] #167 new actions of status devices: use, recycling, refurbish and management. +- [changes] #177 new structure of trade. +- [bugfix] #184 clean nested of schemas of lot ## [1.0.9-beta] -- [addend] #159 external document as proof of erase of disk -- [addend] #162 adding lot for devices unassigned +- [added] #159 external document as proof of erase of disk +- [added] #162 adding lot for devices unassigned ## [1.0.8-beta] - [bugfix] #161 fixing DataStorage with bigInteger ## [1.0.7-beta] -- [addend] #158 support for encrypted snapshots data -- [addend] #135 adding trade system -- [addend] #140 adding endpoint for download the settings for usb workbench +- [added] #158 support for encrypted snapshots data +- [added] #135 adding trade system +- [added] #140 adding endpoint for download the settings for usb workbench ## [1.0.6-beta] - [bugfix] #143 biginteger instead of integer in TestDataStorage ## [1.0.5-beta] -- [addend] #124 adding endpoint for extract the internal stats of use -- [addend] #122 system for verify all documents that it's produced from devicehub -- [addend] #127 add one code for every named tag -- [addend] #131 add one code for every device +- [added] #124 adding endpoint for extract the internal stats of use +- [added] #122 system for verify all documents that it's produced from devicehub +- [added] #127 add one code for every named tag +- [added] #131 add one code for every device - [bugfix] #138 search device with devicehubId ## [1.0.4-beta] -- [addend] #95 adding endpoint for check the hash of one report -- [addend] #98 adding endpoint for insert a new live -- [addend] #98 adding endpoint for get all licences in one query -- [addend] #102 adding endpoint for download metrics +- [added] #95 adding endpoint for check the hash of one report +- [added] #98 adding endpoint for insert a new live +- [added] #98 adding endpoint for get all licences in one query +- [added] #102 adding endpoint for download metrics - [bugfix] #100 fixing bug of scheme live - [bugfix] #101 fixing bug when 2 users have one device and launch one live - [changes] #114 clean blockchain of all models @@ -52,11 +54,11 @@ ml). - [remove] #114 remove proof system ## [1.0.3-beta] -- [addend] #85 add mac of network adapter to device hid +- [added] #85 add mac of network adapter to device hid - [changed] #94 change form of snapshot manual ## [1.0.2-beta] -- [addend] #87 allocate, deallocate and live actions +- [added] #87 allocate, deallocate and live actions - [fixed] #89 save json on disk only for shapshots -- [addend] #83 add owner_id in all kind of device +- [added] #83 add owner_id in all kind of device - [fixed] #91 The most old time allow is 1970-01-01 diff --git a/ereuse_devicehub/dummy/dummy.py b/ereuse_devicehub/dummy/dummy.py index 04fbdaf3..5b6ab47a 100644 --- a/ereuse_devicehub/dummy/dummy.py +++ b/ereuse_devicehub/dummy/dummy.py @@ -139,7 +139,7 @@ class Dummy: res=Lot, item='{}/devices'.format(lot_user['id']), query=[('id', pc) for pc in itertools.islice(pcs, 1, 4)]) - assert len(lot['devices']) + # assert len(lot['devices']) lot2, _ = user2.post({}, res=Lot, diff --git a/ereuse_devicehub/migrations/versions/1bb2b5e0fae7_change_action_device.py b/ereuse_devicehub/migrations/versions/1bb2b5e0fae7_change_action_device.py new file mode 100644 index 00000000..d03df285 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/1bb2b5e0fae7_change_action_device.py @@ -0,0 +1,69 @@ +""" +change action_device + +Revision ID: 1bb2b5e0fae7 +Revises: a0978ac6cf4a +Create Date: 2021-11-04 10:32:49.116399 + +""" +import sqlalchemy as sa +from alembic import context +from alembic import op +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = '1bb2b5e0fae7' +down_revision = 'a0978ac6cf4a' +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_data(): + con = op.get_bind() + + values = f"action_id, {get_inv()}.action.created" + table = f"{get_inv()}.action_device" + joins = f"inner join {get_inv()}.action" + on = f"on {get_inv()}.action_device.action_id = {get_inv()}.action.id" + sql = f"select {values} from {table} {joins} {on}" + + actions_devs = con.execute(sql) + for a in actions_devs: + action_id = a.action_id + created = a.created + sql = f"update {get_inv()}.action_device set created='{created}' where action_id='{action_id}';" + con.execute(sql) + + +def upgrade(): + op.add_column('action_device', + sa.Column('created', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, comment='When Devicehub created this.'), + schema=f'{get_inv()}') + + op.add_column('action_status', + sa.Column('trade_id', postgresql.UUID(as_uuid=True), nullable=True), + schema=f'{get_inv()}') + + op.create_foreign_key("fk_action_status_trade", + "action_status", "trade", + ["trade_id"], ["id"], + ondelete="SET NULL", + source_schema=f'{get_inv()}', + referent_schema=f'{get_inv()}') + + upgrade_data() + + +def downgrade(): + op.drop_constraint("fk_action_status_trade", "action_status", type_="foreignkey", schema=f'{get_inv()}') + op.drop_column('action_device', 'created', schema=f'{get_inv()}') + op.drop_column('action_status', 'trade_id', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/migrations/versions/968b79fa7756_upgrade_confirmrevoke.py b/ereuse_devicehub/migrations/versions/968b79fa7756_upgrade_confirmrevoke.py new file mode 100644 index 00000000..bd3a995d --- /dev/null +++ b/ereuse_devicehub/migrations/versions/968b79fa7756_upgrade_confirmrevoke.py @@ -0,0 +1,51 @@ +"""upgrade confirmrevoke + +Revision ID: 968b79fa7756 +Revises: d22d230d2850 +Create Date: 2021-11-12 19:18:39.135386 + +""" +from alembic import op +from alembic import context +import sqlalchemy as sa +import sqlalchemy_utils +import citext +import teal + + +# revision identifiers, used by Alembic. +revision = '968b79fa7756' +down_revision = 'd22d230d2850' +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(): + con = op.get_bind() + + + confirmsRevokes_sql = f"select * from {get_inv()}.action as action join {get_inv()}.confirm as confirm on action.id=confirm.id where action.type='ConfirmRevoke'" + revokes_sql = f"select confirm.id, confirm.action_id from {get_inv()}.action as action join {get_inv()}.confirm as confirm on action.id=confirm.id where action.type='Revoke'" + confirmsRevokes = [a for a in con.execute(confirmsRevokes_sql)] + revokes = {ac.id: ac.action_id for ac in con.execute(revokes_sql)} + + for ac in confirmsRevokes: + ac_id = ac.id + revoke_id = ac.action_id + trade_id = revokes[revoke_id] + sql_action = f"update {get_inv()}.action set type='Revoke' where id='{ac_id}'" + sql_confirm = f"update {get_inv()}.confirm set action_id='{trade_id}' where id='{ac_id}'" + con.execute(sql_action) + con.execute(sql_confirm) + + + + +def downgrade(): + pass diff --git a/ereuse_devicehub/migrations/versions/d22d230d2850_adding_author_action_device.py b/ereuse_devicehub/migrations/versions/d22d230d2850_adding_author_action_device.py new file mode 100644 index 00000000..ed58f669 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/d22d230d2850_adding_author_action_device.py @@ -0,0 +1,43 @@ +"""adding author action_device + +Revision ID: d22d230d2850 +Revises: 1bb2b5e0fae7 +Create Date: 2021-11-10 17:37:12.304853 + +""" +import sqlalchemy as sa +from alembic import context +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'd22d230d2850' +down_revision = '1bb2b5e0fae7' +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.add_column('action_device', + sa.Column('author_id', + postgresql.UUID(), + nullable=True), + schema=f'{get_inv()}') + op.create_foreign_key("fk_action_device_author", + "action_device", "user", + ["author_id"], ["id"], + ondelete="SET NULL", + source_schema=f'{get_inv()}', + referent_schema='common') + + +def downgrade(): + op.drop_constraint("fk_action_device_author", "device", type_="foreignkey", schema=f'{get_inv()}') + op.drop_column('action_device', 'author_id', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/__init__.py b/ereuse_devicehub/resources/action/__init__.py index 47fa92ad..b405e164 100644 --- a/ereuse_devicehub/resources/action/__init__.py +++ b/ereuse_devicehub/resources/action/__init__.py @@ -285,16 +285,16 @@ class ConfirmDef(ActionDef): SCHEMA = schemas.Confirm -class ConfirmRevokeDef(ActionDef): - VIEW = None - SCHEMA = schemas.ConfirmRevoke - - class RevokeDef(ActionDef): VIEW = None SCHEMA = schemas.Revoke +class ConfirmRevokeDef(ActionDef): + VIEW = None + SCHEMA = schemas.ConfirmRevoke + + class TradeDef(ActionDef): VIEW = None SCHEMA = schemas.Trade diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 44ee0397..ce88ed4b 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -49,6 +49,7 @@ from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, Bio 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 TradeDocument +from ereuse_devicehub.resources.device.metrics import TradeMetrics class JoinedTableMixin: @@ -303,6 +304,31 @@ class ActionDevice(db.Model): device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDevices.id), primary_key=True) + device = relationship(Device, + backref=backref('actions_device', + lazy=True), + primaryjoin=Device.id == device_id) + action = relationship(Action, + backref=backref('actions_device', + lazy=True), + primaryjoin=Action.id == action_id) + created = db.Column(db.TIMESTAMP(timezone=True), + nullable=False, + index=True, + server_default=db.text('CURRENT_TIMESTAMP')) + created.comment = """When Devicehub created this.""" + author_id = Column(UUID(as_uuid=True), + ForeignKey(User.id), + nullable=False, + default=lambda: g.user.id) + # todo compute the org + author = relationship(User, + backref=backref('authored_actions_device', lazy=True, collection_class=set), + primaryjoin=author_id == User.id) + + def __init__(self, **kwargs) -> None: + self.created = kwargs.get('created', datetime.now(timezone.utc)) + super().__init__(**kwargs) class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): @@ -1359,6 +1385,16 @@ class ActionStatus(JoinedTableMixin, ActionWithMultipleTradeDocuments): default=lambda: g.user.id) rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id) rol_user_comment = """The user that .""" + trade_id = db.Column(UUID(as_uuid=True), + db.ForeignKey('trade.id'), + nullable=True) + trade = db.relationship('Trade', + backref=backref('status_changes', + uselist=True, + lazy=True, + order_by=lambda: Action.end_time, + collection_class=list), + primaryjoin='ActionStatus.trade_id == Trade.id') class Recycling(ActionStatus): @@ -1582,11 +1618,11 @@ class Revoke(Confirm): """Users can revoke one confirmation of one action trade""" -class ConfirmRevoke(Confirm): - """Users can confirm and accept one action revoke""" +# class ConfirmRevoke(Confirm): +# """Users can confirm and accept one action revoke""" - def __repr__(self) -> str: - return '<{0.t} {0.id} accepted by {0.user}>'.format(self) +# def __repr__(self) -> str: +# return '<{0.t} {0.id} accepted by {0.user}>'.format(self) class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments): @@ -1632,6 +1668,16 @@ class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments): cascade=CASCADE_OWN), primaryjoin='Trade.lot_id == Lot.id') + def get_metrics(self): + """ + This method get a list of values for calculate a metrics from a spreadsheet + """ + metrics = [] + for doc in self.documents: + m = TradeMetrics(document=doc, Trade=self) + metrics.extend(m.get_metrics()) + return metrics + def __repr__(self) -> str: return '<{0.t} {0.id} executed by {0.author}>'.format(self) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 4ed5e3e9..33b8e7df 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -439,17 +439,21 @@ class ActionStatus(Action): @pre_load def put_devices(self, data: dict): - if not 'devices' in data.keys(): + if 'devices' not in data.keys(): data['devices'] = [] @post_load def put_rol_user(self, data: dict): for dev in data['devices']: - if dev.trading in [None, 'Revoke', 'ConfirmRevoke']: + trades = [ac for ac in dev.actions if ac.t == 'Trade'] + if not trades: return data - trade = [ac for ac in dev.actions if ac.t == 'Trade'][-1] - if trade.user_to != g.user: + + trade = trades[-1] + + if trade.user_from == g.user: data['rol_user'] = trade.user_to + data['trade'] = trade class Recycling(ActionStatus): @@ -581,6 +585,10 @@ class Revoke(ActionWithMultipleDevices): raise ValidationError(txt) +class ConfirmRevoke(Revoke): + pass + + class ConfirmDocument(ActionWithMultipleDocuments): __doc__ = m.Confirm.__doc__ action = NestedOn('Action', only_query='id') @@ -635,7 +643,7 @@ class RevokeDocument(ActionWithMultipleDocuments): class ConfirmRevokeDocument(ActionWithMultipleDocuments): - __doc__ = m.ConfirmRevoke.__doc__ + __doc__ = m.ConfirmRevokeDocument.__doc__ action = NestedOn('Action', only_query='id') @validates_schema @@ -662,66 +670,6 @@ class ConfirmRevokeDocument(ActionWithMultipleDocuments): data['action'] = doc.actions[-1] -class ConfirmRevoke(ActionWithMultipleDevices): - __doc__ = m.ConfirmRevoke.__doc__ - action = NestedOn('Action', only_query='id') - - @validates_schema - def validate_revoke(self, data: dict): - 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 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__ 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 af44cedb..87a5dc81 100644 --- a/ereuse_devicehub/resources/action/views/trade.py +++ b/ereuse_devicehub/resources/action/views/trade.py @@ -3,7 +3,7 @@ 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, +from ereuse_devicehub.resources.action.models import (Trade, Confirm, Revoke, RevokeDocument, ConfirmDocument, ConfirmRevokeDocument) from ereuse_devicehub.resources.user.models import User @@ -66,7 +66,7 @@ class TradeView(): # check than the user than want to do the action is one of the users # involved in the action - if not g.user in [self.trade.user_from, self.trade.user_to]: + if g.user not in [self.trade.user_from, self.trade.user_to]: txt = "You do not participate in this trading" raise ValidationError(txt) @@ -181,17 +181,17 @@ class ConfirmView(ConfirmMixin): then remove the list this device of the list of devices of this action """ real_devices = [] + trade = data['action'] + lot = trade.lot 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) + if dev.trading(lot, simple=True) not in ['NeedConfirmation', 'NeedConfirmRevoke']: + raise ValidationError('Some devices not possible confirm.') # Change the owner for every devices for dev in data['devices']: - user_to = data['action'].user_to - dev.change_owner(user_to) + if dev.trading(lot) == 'NeedConfirmation': + user_to = data['action'].user_to + dev.change_owner(user_to) class RevokeView(ConfirmMixin): @@ -215,57 +215,12 @@ class RevokeView(ConfirmMixin): def validate(self, data): """All devices need to have the status of DoubleConfirmation.""" - ### check ### - if not data['devices']: + devices = data['devices'] + if not devices: raise ValidationError('Devices not exist.') - for dev in data['devices']: - if not dev.trading == 'TradeConfirmed': - txt = 'Some of devices do not have enough to confirm for to do a revoke' - ValidationError(txt) - ### End check ### - - ids = {d.id for d in data['devices']} lot = data['action'].lot - self.model = delete_from_trade(lot, ids) - - -class ConfirmRevokeView(ConfirmMixin): - """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) + self.model = delete_from_trade(lot, devices) class ConfirmDocumentMixin(): diff --git a/ereuse_devicehub/resources/action/views/views.py b/ereuse_devicehub/resources/action/views/views.py index 415effb8..88c95438 100644 --- a/ereuse_devicehub/resources/action/views/views.py +++ b/ereuse_devicehub/resources/action/views/views.py @@ -15,7 +15,7 @@ from ereuse_devicehub.db import db from ereuse_devicehub.query import things_response from ereuse_devicehub.resources.action.models import (Action, Snapshot, VisualTest, InitTransfer, Live, Allocate, Deallocate, - Trade, Confirm, ConfirmRevoke, Revoke) + Trade, Confirm, Revoke) from ereuse_devicehub.resources.action.views import trade as trade_view from ereuse_devicehub.resources.action.views.snapshot import SnapshotView, save_json, move_json from ereuse_devicehub.resources.action.views.documents import ErasedView @@ -235,9 +235,9 @@ class ActionView(View): revoke = trade_view.RevokeView(json, resource_def, self.schema) return revoke.post() - if json['type'] == ConfirmRevoke.t: - confirm_revoke = trade_view.ConfirmRevokeView(json, resource_def, self.schema) - return confirm_revoke.post() + if json['type'] == 'ConfirmRevoke': + revoke = trade_view.RevokeView(json, resource_def, self.schema) + return revoke.post() if json['type'] == 'RevokeDocument': revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema) diff --git a/ereuse_devicehub/resources/device/metrics.py b/ereuse_devicehub/resources/device/metrics.py new file mode 100644 index 00000000..f9399f95 --- /dev/null +++ b/ereuse_devicehub/resources/device/metrics.py @@ -0,0 +1,263 @@ +import copy + + +class MetricsMix: + """we want get the data metrics of one device""" + + def __init__(self, *args, **kwargs): + # self.actions.sort(key=lambda x: x.created) + self.rows = [] + self.lifetime = 0 + self.last_trade = None + self.action_create_by = 'Receiver' + self.status_receiver = '' + self.status_supplier = '' + self.act = None + self.end_users = 0 + self.final_user_code = '' + self.trades = {} + + def get_template_row(self): + """ + This is a template of a row. + """ + return {'type': '', + 'action_type': 'Status', + 'document_name': '', + 'status_receiver': self.status_receiver, + 'status_supplier': self.status_supplier, + 'status_receiver_created': '', + 'status_supplier_created': '', + 'trade_supplier': '', + 'trade_receiver': self.act.author.email, + 'trade_confirmed': '', + 'trade_weight': 0, + 'action_create_by': self.action_create_by, + 'devicehubID': self.devicehub_id, + 'hid': self.hid, + 'finalUserCode': '', + 'numEndUsers': 0, + 'liveCreate': 0, + 'usageTimeHdd': self.lifetime, + 'created': self.act.created, + 'start': '', + 'usageTimeAllocate': 0} + + def get_metrics(self): + """ + This method get a list of values for calculate a metrics from a spreadsheet + """ + return self.rows + + +class Metrics(MetricsMix): + """we want get the data metrics of one device""" + + def __init__(self, *args, **kwargs): + self.device = kwargs.pop('device') + self.actions = copy.copy(self.device.actions) + super().__init__(*args, **kwargs) + self.hid = self.device.hid + self.devicehub_id = self.device.devicehub_id + + def get_action_status(self): + """ + Mark the status of one device. + If exist one trade before this action, then modify the trade action + else, create one new row. + """ + if self.act.trade not in self.trades: + # If not exist one trade, the status is of the Receive + self.action_create_by = 'Receiver' + self.status_receiver = self.act.type + self.status_supplier = '' + row = self.get_template_row() + row['status_supplier_created'] = '' + row['status_receiver_created'] = self.act.created + self.rows.append(row) + return + + trade = self.trades[self.act.trade] + + if trade['trade_supplier'] == self.act.author.email: + trade['status_supplier'] = self.act.type + trade['status_supplier_created'] = self.act.created + return + + if trade['trade_receiver'] == self.act.author.email: + trade['status_receiver'] = self.act.type + trade['status_receiver_created'] = self.act.created + return + + # necesitamos poder poner un cambio de estado de un trade mas antiguo que last_trade + # lo mismo con confirm + + def get_snapshot(self): + """ + If there are one snapshot get the last lifetime for to do a calcul of time of use. + """ + lifestimes = self.act.get_last_lifetimes() + if lifestimes: + self.lifetime = lifestimes[0]['lifetime'] + + def get_allocate(self): + """ + If the action is one Allocate, need modify the row base. + """ + self.action_create_by = 'Receiver' + self.end_users = self.act.end_users + self.final_user_code = self.act.final_user_code + row = self.get_template_row() + row['type'] = 'Allocate' + row['trade_supplier'] = '' + row['finalUserCode'] = self.final_user_code + row['numEndUsers'] = self.end_users + row['start'] = self.act.start_time + row['usageTimeAllocate'] = self.lifetime + self.rows.append(row) + + def get_live(self): + """ + If the action is one Live, need modify the row base. + """ + self.action_create_by = 'Receiver' + row = self.get_template_row() + row['type'] = 'Live' + row['finalUserCode'] = self.final_user_code + row['numEndUsers'] = self.end_users + row['start'] = self.act.start_time + row['usageTimeAllocate'] = self.lifetime + row['liveCreate'] = self.act.created + if self.act.usage_time_hdd: + row['usageTimeHdd'] = self.act.usage_time_hdd.total_seconds() / 3600 + self.rows.append(row) + + def get_deallocate(self): + """ + If the action is one Dellocate, need modify the row base. + """ + self.action_create_by = 'Receiver' + row = self.get_template_row() + row['type'] = 'Deallocate' + row['start'] = self.act.start_time + self.rows.append(row) + + def get_confirms(self): + """ + if the action is one trade action, is possible than have a list of confirmations. + Get the doble confirm for to know if this trade is confirmed or not. + """ + return self.device.trading(self.act.lot, simple=True) + + def get_trade(self): + """ + If this action is a trade action modify the base row. + """ + if self.act.author == self.act.user_from: + self.action_create_by = 'Supplier' + self.status_receiver = '' + + row = self.get_template_row() + self.last_trade = row + row['type'] = 'Trade' + row['action_type'] = 'Trade' + row['trade_supplier'] = self.act.user_from.email + row['trade_receiver'] = self.act.user_to.email + row['status_receiver'] = self.status_receiver + row['status_supplier'] = '' + row['trade_confirmed'] = self.get_confirms() + self.trades[self.act] = row + self.rows.append(row) + + def get_metrics(self): + """ + This method get a list of values for calculate a metrics from a spreadsheet + """ + for act in self.actions: + self.act = act + if act.type in ['Use', 'Refurbish', 'Recycling', 'Management']: + self.get_action_status() + continue + + if act.type == 'Snapshot': + self.get_snapshot() + continue + + if act.type == 'Allocate': + self.get_allocate() + continue + + if act.type == 'Live': + self.get_live() + continue + + if act.type == 'Deallocate': + self.get_deallocate() + continue + + if act.type == 'Trade': + self.get_trade() + continue + + return self.rows + + +class TradeMetrics(MetricsMix): + """we want get the data metrics of one device""" + + def __init__(self, *args, **kwargs): + self.document = kwargs.pop('document') + self.actions = copy.copy(self.document.actions) + self.hid = self.document.file_hash + self.devicehub_id = '' + super().__init__(*args, **kwargs) + + def get_metrics(self): + self.last_trade = next(x for x in self.actions if x.t == 'Trade') + self.act = self.last_trade + row = self.get_template_row() + + row['type'] = 'Trade-Document' + row['action_type'] = 'Trade-Document' + if self.document.weight: + row['type'] = 'Trade-Container' + row['action_type'] = 'Trade-Container' + + row['document_name'] = self.document.file_name + row['trade_supplier'] = self.last_trade.user_from.email + row['trade_receiver'] = self.last_trade.user_to.email + row['trade_confirmed'] = self.get_confirms() + row['status_receiver'] = '' + row['status_supplier'] = '' + row['trade_weight'] = self.document.weight + if self.document.owner == self.last_trade.user_from: + row['action_create_by'] = 'Supplier' + elif self.document.owner == self.last_trade.user_to: + row['action_create_by'] = 'Receiver' + + self.rows.append(row) + + return self.rows + + def get_confirms(self): + """ + if the action is one trade action, is possible than have a list of confirmations. + Get the doble confirm for to know if this trade is confirmed or not. + """ + trade = None + confirmations = [] + confirms = [] + for ac in self.document.actions: + if ac.t == 'Trade': + trade = ac + elif ac.t == 'ConfirmDocument': + confirms.append(ac.author) + confirmations.append(ac) + elif ac.t in ['RevokeDocument', 'ConfirmDocumentRevoke']: + confirmations.append(ac) + + if confirmations and confirmations[-1].t == 'ConfirmDocument': + if trade.user_from in confirms and trade.user_to in confirms: + return True + + return False diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index e9a234ed..a97c7392 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -1,17 +1,17 @@ import pathlib import copy +import time from flask import g from contextlib import suppress from fractions import Fraction from itertools import chain from operator import attrgetter from typing import Dict, List, Set +from flask_sqlalchemy import event from boltons import urlutils from citext import CIText -from flask_sqlalchemy import event from ereuse_utils.naming import HID_CONVERSION_DOC, Naming -from flask import g from more_itertools import unique_everseen from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \ Sequence, SmallInteger, Unicode, inspect, text @@ -34,11 +34,12 @@ from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, Co DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.device.metrics import Metrics def create_code(context): - _id = Device.query.order_by(Device.id.desc()).first() or 1 - if not _id == 1: + _id = Device.query.order_by(Device.id.desc()).first() or 3 + if not _id == 3: _id = _id.id + 1 return hashcode.encode(_id) @@ -173,7 +174,16 @@ class Device(Thing): Actions are returned by descending ``created`` time. """ - return sorted(chain(self.actions_multiple, self.actions_one), key=lambda x: x.created) + actions_multiple = copy.copy(self.actions_multiple) + actions_one = copy.copy(self.actions_one) + + for ac in actions_multiple: + ac.real_created = ac.actions_device[0].created + + for ac in actions_one: + ac.real_created = ac.created + + return sorted(chain(actions_multiple, actions_one), key=lambda x: x.real_created) @property def problems(self): @@ -208,10 +218,10 @@ class Device(Thing): if isinstance(c, ColumnProperty) and not getattr(c, 'foreign_keys', None) and c.key not in self._NON_PHYSICAL_PROPS} - + @property def public_properties(self) -> Dict[str, object or None]: - """Fields that describe the properties of a device than next show + """Fields that describe the properties of a device than next show in the public page. :return A dictionary: @@ -232,7 +242,7 @@ class Device(Thing): :return a list of actions: """ hide_actions = ['Price', 'EreusePrice'] - actions = [ac for ac in self.actions if not ac.t in hide_actions] + actions = [ac for ac in self.actions if ac.t not in hide_actions] actions.reverse() return actions @@ -289,7 +299,7 @@ class Device(Thing): status_actions = [ac.t for ac in states.Status.actions()] history = [] for ac in self.actions: - if not ac.t in status_actions: + if ac.t not in status_actions: continue if not history: history.append(ac) @@ -304,75 +314,90 @@ class Device(Thing): return history @property - def trading(self): + def tradings(self): + return {str(x.id): self.trading(x.lot) for x in self.actions if x.t == 'Trade'} + + def trading(self, lot, simple=None): """The trading state, or None if no Trade action has - ever been performed to this device. This extract the posibilities for to do""" + ever been performed to this device. This extract the posibilities for to do. + This method is performed for show in the web. + If you need to do one simple and generic response you can put simple=True for that.""" + if not hasattr(lot, 'trade'): + return - # trade = 'Trade' - confirm = 'Confirm' - need_confirm = 'NeedConfirmation' - double_confirm = 'TradeConfirmed' - revoke = 'Revoke' - revoke_pending = 'RevokePending' - confirm_revoke = 'ConfirmRevoke' - # revoke_confirmed = 'RevokeConfirmed' + Status = {0: 'Trade', + 1: 'Confirm', + 2: 'NeedConfirmation', + 3: 'TradeConfirmed', + 4: 'Revoke', + 5: 'NeedConfirmRevoke', + 6: 'RevokeConfirmed'} - # return the correct status of trade depending of the user + trade = lot.trade + user_from = trade.user_from + user_to = trade.user_to + status = 0 + last_user = None - ##### CASES ##### - ## User1 == owner of trade (This user have automatic Confirmation) - ## ======================= - ## if the last action is => only allow to do - ## ========================================== - ## Confirmation not User1 => Revoke - ## Confirmation User1 => Revoke - ## Revoke not User1 => ConfirmRevoke - ## Revoke User1 => RevokePending - ## RevokeConfirmation => RevokeConfirmed - ## - ## - ## User2 == Not owner of trade - ## ======================= - ## if the last action is => only allow to do - ## ========================================== - ## Confirmation not User2 => Confirm - ## Confirmation User2 => Revoke - ## Revoke not User2 => ConfirmRevoke - ## Revoke User2 => RevokePending - ## RevokeConfirmation => RevokeConfirmed + if not hasattr(trade, 'acceptances'): + return Status[status] - ac = self.last_action_trading - if not ac: - return + for ac in self.actions: + if ac.t not in ['Confirm', 'Revoke']: + continue - first_owner = self.which_user_put_this_device_in_trace() + if ac.user not in [user_from, user_to]: + continue - if ac.type == confirm_revoke: - # can to do revoke_confirmed - return confirm_revoke + if ac.t == 'Confirm' and ac.action == trade: + if status in [0, 6]: + if simple: + status = 2 + continue + status = 1 + last_user = ac.user + if ac.user == user_from and user_to == g.user: + status = 2 + if ac.user == user_to and user_from == g.user: + status = 2 + continue - if ac.type == revoke: - if ac.user == g.user: - # can todo revoke_pending - return revoke_pending - else: - # can to do confirm_revoke - return revoke + if status in [1, 2]: + if last_user != ac.user: + status = 3 + last_user = ac.user + continue - if ac.type == confirm: - if not first_owner: - return + if status in [4, 5]: + status = 3 + last_user = ac.user + continue - if ac.user == first_owner: - if first_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 + if ac.t == 'Revoke' and ac.action == trade: + if status == 3: + if simple: + status = 5 + continue + status = 4 + last_user = ac.user + if ac.user == user_from and user_to == g.user: + status = 5 + if ac.user == user_to and user_from == g.user: + status = 5 + continue + + if status in [4, 5]: + if last_user != ac.user: + status = 6 + last_user = ac.user + continue + + if status in [1, 2]: + status = 6 + last_user = ac.user + continue + + return Status[status] @property def revoke(self): @@ -428,8 +453,8 @@ class Device(Thing): # TODO @cayop uncomment this lines for link the possessor with the device # from ereuse_devicehub.resources.action.models import Receive # with suppress(LookupError): - # action = self.last_action_of(Receive) - # return action.agent_to + # action = self.last_action_of(Receive) + # return action.agent_to @property def working(self): @@ -478,15 +503,15 @@ class Device(Thing): def which_user_put_this_device_in_trace(self): """which is the user than put this device in this trade""" actions = copy.copy(self.actions) - actions.sort(key=lambda x: x.created) actions.reverse() - last_ac = None # search the automatic Confirm for ac in actions: if ac.type == 'Trade': - return last_ac.user - if ac.type == 'Confirm': - last_ac = ac + action_device = [x for x in ac.actions_device if x.device == self][0] + if action_device.author: + return action_device.author + + return ac.author def change_owner(self, new_user): """util for change the owner one device""" @@ -507,52 +532,8 @@ class Device(Thing): """ This method get a list of values for calculate a metrics from a spreadsheet """ - actions = copy.copy(self.actions) - actions.sort(key=lambda x: x.created) - allocates = [] - lifetime = 0 - for act in actions: - if act.type == 'Snapshot': - snapshot = act - lifestimes = snapshot.get_last_lifetimes() - lifetime = 0 - if lifestimes: - lifetime = lifestimes[0]['lifetime'] - - if act.type == 'Allocate': - allo = {'type': 'Allocate', - 'devicehubID': self.devicehub_id, - 'finalUserCode': act.final_user_code, - 'numEndUsers': act.end_users, - 'hid': self.hid, - 'liveCreate': 0, - 'usageTimeHdd': 0, - 'start': act.start_time, - 'usageTimeAllocate': lifetime} - allocates.append(allo) - - if act.type == 'Live': - allocate = copy.copy(allo) - allocate['type'] = 'Live' - allocate['liveCreate'] = act.created - allocate['usageTimeHdd'] = 0 - if act.usage_time_hdd: - allocate['usageTimeHdd'] = act.usage_time_hdd.total_seconds()/3600 - allocates.append(allocate) - - if act.type == 'Deallocate': - deallo = {'type': 'Deallocate', - 'devicehubID': self.devicehub_id, - 'finalUserCode': '', - 'numEndUsers': '', - 'hid': self.hid, - 'liveCreate': 0, - 'usageTimeHdd': lifetime, - 'start': act.start_time, - 'usageTimeAllocate': 0} - allocates.append(deallo) - - return allocates + metrics = Metrics(device=self) + return metrics.get_metrics() def __lt__(self, other): return self.id < other.id @@ -681,7 +662,13 @@ class Computer(Device): @property def actions(self) -> list: - return sorted(chain(super().actions, self.actions_parent)) + actions = copy.copy(super().actions) + actions_parent = copy.copy(self.actions_parent) + for ac in actions_parent: + ac.real_created = ac.created + + return sorted(chain(actions, actions_parent), key=lambda x: x.real_created) + # return sorted(chain(super().actions, self.actions_parent)) @property def ram_size(self) -> int: @@ -751,7 +738,7 @@ class Computer(Device): return urls def add_mac_to_hid(self, components_snap=None): - """Returns the Naming.hid with the first mac of network adapter, + """Returns the Naming.hid with the first mac of network adapter, following an alphabetical order. """ self.set_hid() @@ -884,7 +871,7 @@ class Component(Device): """ assert self.hid is None, 'Don\'t use this method with a component that has HID' component = self.__class__.query \ - .filter_by(parent=parent, hid=None, owner_id=self.owner_id, + .filter_by(parent=parent, hid=None, owner_id=self.owner_id, **self.physical_properties) \ .filter(~Component.id.in_(blacklist)) \ .first() @@ -1212,3 +1199,15 @@ class Manufacturer(db.Model): listener_reset_field_updated_in_actual_time(Device) + +def create_code_tag(mapper, connection, device): + """ + This function create a new tag every time than one device is create. + this tag is the same of devicehub_id. + """ + from ereuse_devicehub.resources.tag.model import Tag + tag = Tag(device_id=device.id, id=device.devicehub_id) + db.session.add(tag) + + +event.listen(Device, 'after_insert', create_code_tag, propagate=True) diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index d9a658fe..0c265d78 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -1,7 +1,7 @@ import datetime from marshmallow import post_load, pre_load, fields as f -from marshmallow.fields import Boolean, Date, DateTime, Float, Integer, List, Str, String, UUID +from marshmallow.fields import Boolean, Date, DateTime, Float, Integer, List, Str, String, UUID, Dict from marshmallow.validate import Length, OneOf, Range from sqlalchemy.util import OrderedSet from stdnum import imei, meid @@ -40,22 +40,24 @@ class Device(Thing): width = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment) height = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment) depth = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.depth.comment) + # TODO TimeOut 2. Comment actions and lots if there are time out. actions = NestedOn('Action', many=True, dump_only=True, description=m.Device.actions.__doc__) + # TODO TimeOut 2. Comment actions_one and lots if there are time out. actions_one = NestedOn('Action', many=True, load_only=True, collection_class=OrderedSet) problems = NestedOn('Action', many=True, dump_only=True, description=m.Device.problems.__doc__) url = URL(dump_only=True, description=m.Device.url.__doc__) + # TODO TimeOut 2. Comment actions and lots if there are time out. lots = NestedOn('Lot', many=True, dump_only=True, 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 = SanitizedStr(dump_only=True, description='') + tradings = Dict(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__) + traking = EnumField(states.Traking, dump_only=True, description=m.Device.physical.__doc__) usage = EnumField(states.Usage, dump_only=True, description=m.Device.physical.__doc__) - revoke = UUID(dump_only=True) + revoke = UUID(dump_only=True) physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor') production_date = DateTime('iso', description=m.Device.updated.comment, @@ -99,6 +101,7 @@ class Device(Thing): class Computer(Device): __doc__ = m.Computer.__doc__ + # TODO TimeOut 1. Comment components if there are time out. components = NestedOn('Component', many=True, dump_only=True, @@ -129,7 +132,7 @@ class Computer(Device): description=m.Computer.privacy.__doc__) amount = Integer(validate=f.validate.Range(min=0, max=100), description=m.Computer.amount.__doc__) - # author_id = NestedOn(s_user.User,only_query='author_id') + # author_id = NestedOn(s_user.User, only_query='author_id') owner_id = UUID(data_key='ownerID') transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment) receiver_id = UUID(data_key='receiverID') diff --git a/ereuse_devicehub/resources/device/states.py b/ereuse_devicehub/resources/device/states.py index 5177f701..3cfefe4b 100644 --- a/ereuse_devicehub/resources/device/states.py +++ b/ereuse_devicehub/resources/device/states.py @@ -37,7 +37,6 @@ class Trading(State): Trade = e.Trade Confirm = e.Confirm Revoke = e.Revoke - ConfirmRevoke = e.ConfirmRevoke Cancelled = e.CancelTrade Sold = e.Sell Donated = e.Donate diff --git a/ereuse_devicehub/resources/documents/device_row.py b/ereuse_devicehub/resources/documents/device_row.py index 4e683ef9..26eecd21 100644 --- a/ereuse_devicehub/resources/documents/device_row.py +++ b/ereuse_devicehub/resources/documents/device_row.py @@ -414,6 +414,8 @@ def none2str(string): return '' return format(string) + + def get_action(component, action): """ Filter one action from a component or return None """ result = [a for a in component.actions if a.type == action] @@ -427,9 +429,21 @@ class ActionRow(OrderedDict): # General information about allocates, deallocate and lives self['DHID'] = allocate['devicehubID'] self['Hid'] = allocate['hid'] - self['Start'] = allocate['start'] - self['FinalUserCode'] = allocate['finalUserCode'] - self['NumEndUsers'] = allocate['numEndUsers'] + self['Document-Name'] = allocate['document_name'] + self['Action-Type'] = allocate['action_type'] + self['Action-User-LastOwner-Supplier'] = allocate['trade_supplier'] + self['Action-User-LastOwner-Receiver'] = allocate['trade_receiver'] + self['Action-Create-By'] = allocate['action_create_by'] + self['Trade-Confirmed'] = allocate['trade_confirmed'] + self['Status-Created-By-Supplier-About-Reciber'] = allocate['status_supplier'] + self['Status-Receiver'] = allocate['status_receiver'] + self['Status Supplier – Created Date'] = allocate['status_supplier_created'] + self['Status Receiver – Created Date'] = allocate['status_receiver_created'] + self['Trade-Weight'] = allocate['trade_weight'] + self['Action-Create'] = allocate['created'] + self['Allocate-Start'] = allocate['start'] + self['Allocate-User-Code'] = allocate['finalUserCode'] + self['Allocate-NumUsers'] = allocate['numEndUsers'] self['UsageTimeAllocate'] = allocate['usageTimeAllocate'] self['Type'] = allocate['type'] self['LiveCreate'] = allocate['liveCreate'] diff --git a/ereuse_devicehub/resources/documents/documents.py b/ereuse_devicehub/resources/documents/documents.py index f6de8884..bb7a4834 100644 --- a/ereuse_devicehub/resources/documents/documents.py +++ b/ereuse_devicehub/resources/documents/documents.py @@ -3,11 +3,9 @@ import enum import uuid import time import datetime -import pathlib from collections import OrderedDict from io import StringIO from typing import Callable, Iterable, Tuple -from decouple import config import boltons import flask @@ -32,6 +30,8 @@ from ereuse_devicehub.resources.documents.device_row import (DeviceRow, StockRow InternalStatsRow) from ereuse_devicehub.resources.lot import LotView from ereuse_devicehub.resources.lot.models import Lot +from ereuse_devicehub.resources.action.models import Trade +from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash, verify_hash @@ -90,7 +90,6 @@ class DocumentView(DeviceView): res = flask.make_response(template) return res - @staticmethod def erasure(query: db.Query): def erasures(): @@ -151,7 +150,7 @@ class DevicesDocumentView(DeviceView): class ActionsDocumentView(DeviceView): @cache(datetime.timedelta(minutes=1)) def find(self, args: dict): - query = (x for x in self.query(args) if x.owner_id == g.user.id) + query = (x for x in self.query(args)) return self.generate_post_csv(query) def generate_post_csv(self, query): @@ -159,13 +158,26 @@ class ActionsDocumentView(DeviceView): data = StringIO() cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"') first = True + devs_id = [] for device in query: + devs_id.append(device.id) for allocate in device.get_metrics(): d = ActionRow(allocate) if first: cw.writerow(d.keys()) first = False cw.writerow(d.values()) + query_trade = Trade.query.filter(Trade.devices.any(Device.id.in_(devs_id))).all() + + for trade in query_trade: + data_rows = trade.get_metrics() + for row in data_rows: + d = ActionRow(row) + if first: + cw.writerow(d.keys()) + first = False + cw.writerow(d.values()) + bfile = data.getvalue().encode('utf-8') output = make_response(bfile) insert_hash(bfile) @@ -185,11 +197,11 @@ class LotsDocumentView(LotView): cw = csv.writer(data) first = True for lot in query: - l = LotRow(lot) + _lot = LotRow(lot) if first: - cw.writerow(l.keys()) + cw.writerow(_lot.keys()) first = False - cw.writerow(l.values()) + cw.writerow(_lot.values()) bfile = data.getvalue().encode('utf-8') output = make_response(bfile) insert_hash(bfile) @@ -275,7 +287,7 @@ class StampsView(View): ok = '100% coincidence. The attached file contains data 100% existing in \ to our backend' result = ('Bad', bad) - mime = ['text/csv', 'application/pdf', 'text/plain','text/markdown', + mime = ['text/csv', 'application/pdf', 'text/plain', 'text/markdown', 'image/jpeg', 'image/png', 'text/html', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.oasis.opendocument.spreadsheet', @@ -304,9 +316,9 @@ class InternalStatsView(DeviceView): create = '{}-{}'.format(ac.created.year, ac.created.month) user = ac.author.email - if not user in d: - d[user] = {} - if not create in d[user]: + if user not in d: + d[user] = {} + if create not in d[user]: d[user][create] = [] d[user][create].append(ac) @@ -434,4 +446,3 @@ class DocumentDef(Resource): auth=app.auth) wbconf_view = app.auth.requires_auth(wbconf_view) self.add_url_rule('/wbconf/', view_func=wbconf_view, methods=get) - diff --git a/ereuse_devicehub/resources/lot/schemas.py b/ereuse_devicehub/resources/lot/schemas.py index 5604cc2e..9a5a58aa 100644 --- a/ereuse_devicehub/resources/lot/schemas.py +++ b/ereuse_devicehub/resources/lot/schemas.py @@ -5,14 +5,32 @@ 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 from ereuse_devicehub.resources.schemas import Thing -class Lot(Thing): +TRADE_VALUES = ( + 'id', + 'user_from.email', + 'user_to.email', + 'user_from.id', + 'user_to.id', + 'user_to.code', + 'user_from.code' +) + + +DOCUMENTS_VALUES = ( + 'id', + 'file_name', + 'total_weight', + 'trading' +) + + +class Old_Lot(Thing): id = f.UUID(dump_only=True) name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True) description = SanitizedStr(description=m.Lot.description.comment) @@ -29,4 +47,11 @@ class Lot(Thing): receiver_address = SanitizedStr(validate=f.validate.Length(max=42)) deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True) documents = NestedOn('TradeDocument', many=True, dump_only=True) - trade = NestedOn(s_action.Trade, dump_only=True) + + +class Lot(Thing): + id = f.UUID(dump_only=True) + name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True) + description = SanitizedStr(description=m.Lot.description.comment) + trade = f.Nested(s_action.Trade, dump_only=True, only=TRADE_VALUES) + documents = f.Nested('TradeDocument', many=True, dump_only=True, only=DOCUMENTS_VALUES) diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index fcbca827..dce6af62 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -1,4 +1,5 @@ import uuid +from sqlalchemy.util import OrderedSet from collections import deque from enum import Enum from typing import Dict, List, Set, Union @@ -13,7 +14,7 @@ from teal.resource import View from ereuse_devicehub.db import db from ereuse_devicehub.query import things_response from ereuse_devicehub.resources.device.models import Device, Computer -from ereuse_devicehub.resources.action.models import Trade, Confirm, Revoke, ConfirmRevoke +from ereuse_devicehub.resources.action.models import Trade, Confirm, Revoke from ereuse_devicehub.resources.lot.models import Lot, Path @@ -230,7 +231,7 @@ class LotDeviceView(LotBaseChildrenView): return devices = set(Device.query.filter(Device.id.in_(ids)).filter( - Device.owner==g.user)) + Device.owner == g.user)) lot.devices.update(devices) @@ -246,7 +247,8 @@ class LotDeviceView(LotBaseChildrenView): return if lot.trade: - return delete_from_trade(lot, ids) + devices = Device.query.filter(Device.id.in_(ids)).all() + return delete_from_trade(lot, devices) if not g.user == lot.owner: txt = 'This is not your lot' @@ -258,49 +260,45 @@ class LotDeviceView(LotBaseChildrenView): lot.devices.difference_update(devices) -def delete_from_trade(lot: Lot, ids: Set[int]): - users = [lot.trade.user_from.id, lot.trade.user_to.id] - if not g.user.id in users: +def delete_from_trade(lot: Lot, devices: List): + users = [lot.trade.user_from, lot.trade.user_to] + if g.user not in users: # theoretically this case is impossible txt = 'This is not your trade' raise ma.ValidationError(txt) - devices = set(Device.query.filter(Device.id.in_(ids)).filter( - Device.owner_id.in_(users))) + # we need lock the action revoke for devices than travel for futures trades + for dev in devices: + if dev.owner not in users: + txt = 'This is not your device' + raise ma.ValidationError(txt) - # Now we need to know which devices we need extract of the lot - without_confirms = set() # set of devs without confirms of user2 + drop_of_lot = [] + without_confirms = [] + for dev in devices: + if dev.trading(lot) in ['NeedConfirmation', 'Confirm', 'NeedConfirmRevoke']: + drop_of_lot.append(dev) + dev.reset_owner() - # if the trade need confirmation, then extract all devs than - # have only one confirmation and is from the same user than try to do - # now the revoke action - if lot.trade.confirm: - for dev in devices: - # if have only one confirmation - # then can be revoked and deleted of the lot - # Confirm of dev.trading mean that there are only one confirmation - # and the first user than put this device in trade is the actual g.user - if dev.trading == 'Confirm': - without_confirms.add(dev) - dev.reset_owner() + if not lot.trade.confirm: + drop_of_lot.append(dev) + without_confirms.append(dev) + dev.reset_owner() - # we need to mark one revoke for every devs - revoke = Revoke(action=lot.trade, user=g.user, devices=devices) + revoke = Revoke(action=lot.trade, user=g.user, devices=set(devices)) db.session.add(revoke) - if not lot.trade.confirm: - # if the trade is with phantom account - without_confirms = devices - if without_confirms: - confirm_revoke = ConfirmRevoke( - action=revoke, - user=g.user, - devices=without_confirms + phantom = lot.trade.user_to + if lot.trade.user_to == g.user: + phantom = lot.trade.user_from + + phantom_revoke = Revoke( + action=lot.trade, + user=phantom, + devices=set(without_confirms) ) - db.session.add(confirm_revoke) - - lot.devices.difference_update(without_confirms) - lot.trade.devices = lot.devices + db.session.add(phantom_revoke) + lot.devices.difference_update(OrderedSet(drop_of_lot)) return revoke diff --git a/tests/files/basic.csv b/tests/files/basic.csv index 45750ae9..253838e9 100644 --- a/tests/files/basic.csv +++ b/tests/files/basic.csv @@ -1,2 +1,2 @@ DHID;DocumentID;Public Link;Lots;Tag 1 Type;Tag 1 ID;Tag 1 Organization;Tag 2 Type;Tag 2 ID;Tag 2 Organization;Tag 3 Type;Tag 3 ID;Tag 3 Organization;Device Hardware ID;Device Type;Device Chassis;Device Serial Number;Device Model;Device Manufacturer;Registered in;Registered (process);Updated in (software);Updated in (web);Physical state;Trading state;Processor;RAM (MB);Data Storage Size (MB);Processor 1;Processor 1 Manufacturer;Processor 1 Model;Processor 1 Serial Number;Processor 1 Number of cores;Processor 1 Speed (GHz);Benchmark Processor 1 (points);Benchmark ProcessorSysbench Processor 1 (points);Processor 2;Processor 2 Manufacturer;Processor 2 Model;Processor 2 Serial Number;Processor 2 Number of cores;Processor 2 Speed (GHz);Benchmark Processor 2 (points);Benchmark ProcessorSysbench Processor 2 (points);RamModule 1;RamModule 1 Manufacturer;RamModule 1 Model;RamModule 1 Serial Number;RamModule 1 Size (MB);RamModule 1 Speed (MHz);RamModule 2;RamModule 2 Manufacturer;RamModule 2 Model;RamModule 2 Serial Number;RamModule 2 Size (MB);RamModule 2 Speed (MHz);RamModule 3;RamModule 3 Manufacturer;RamModule 3 Model;RamModule 3 Serial Number;RamModule 3 Size (MB);RamModule 3 Speed (MHz);RamModule 4;RamModule 4 Manufacturer;RamModule 4 Model;RamModule 4 Serial Number;RamModule 4 Size (MB);RamModule 4 Speed (MHz);DataStorage 1;DataStorage 1 Manufacturer;DataStorage 1 Model;DataStorage 1 Serial Number;DataStorage 1 Size (MB);Erasure DataStorage 1;Erasure DataStorage 1 Serial Number;Erasure DataStorage 1 Size (MB);Erasure DataStorage 1 Software;Erasure DataStorage 1 Result;Erasure DataStorage 1 Certificate URL;Erasure DataStorage 1 Type;Erasure DataStorage 1 Method;Erasure DataStorage 1 Elapsed (hours);Erasure DataStorage 1 Date;Erasure DataStorage 1 Steps;Erasure DataStorage 1 Steps Start Time;Erasure DataStorage 1 Steps End Time;Benchmark DataStorage 1 Read Speed (MB/s);Benchmark DataStorage 1 Writing speed (MB/s);Test DataStorage 1 Software;Test DataStorage 1 Type;Test DataStorage 1 Result;Test DataStorage 1 Power on (hours used);Test DataStorage 1 Lifetime remaining (percentage);DataStorage 2;DataStorage 2 Manufacturer;DataStorage 2 Model;DataStorage 2 Serial Number;DataStorage 2 Size (MB);Erasure DataStorage 2;Erasure DataStorage 2 Serial Number;Erasure DataStorage 2 Size (MB);Erasure DataStorage 2 Software;Erasure DataStorage 2 Result;Erasure DataStorage 2 Certificate URL;Erasure DataStorage 2 Type;Erasure DataStorage 2 Method;Erasure DataStorage 2 Elapsed (hours);Erasure DataStorage 2 Date;Erasure DataStorage 2 Steps;Erasure DataStorage 2 Steps Start Time;Erasure DataStorage 2 Steps End Time;Benchmark DataStorage 2 Read Speed (MB/s);Benchmark DataStorage 2 Writing speed (MB/s);Test DataStorage 2 Software;Test DataStorage 2 Type;Test DataStorage 2 Result;Test DataStorage 2 Power on (hours used);Test DataStorage 2 Lifetime remaining (percentage);DataStorage 3;DataStorage 3 Manufacturer;DataStorage 3 Model;DataStorage 3 Serial Number;DataStorage 3 Size (MB);Erasure DataStorage 3;Erasure DataStorage 3 Serial Number;Erasure DataStorage 3 Size (MB);Erasure DataStorage 3 Software;Erasure DataStorage 3 Result;Erasure DataStorage 3 Certificate URL;Erasure DataStorage 3 Type;Erasure DataStorage 3 Method;Erasure DataStorage 3 Elapsed (hours);Erasure DataStorage 3 Date;Erasure DataStorage 3 Steps;Erasure DataStorage 3 Steps Start Time;Erasure DataStorage 3 Steps End Time;Benchmark DataStorage 3 Read Speed (MB/s);Benchmark DataStorage 3 Writing speed (MB/s);Test DataStorage 3 Software;Test DataStorage 3 Type;Test DataStorage 3 Result;Test DataStorage 3 Power on (hours used);Test DataStorage 3 Lifetime remaining (percentage);DataStorage 4;DataStorage 4 Manufacturer;DataStorage 4 Model;DataStorage 4 Serial Number;DataStorage 4 Size (MB);Erasure DataStorage 4;Erasure DataStorage 4 Serial Number;Erasure DataStorage 4 Size (MB);Erasure DataStorage 4 Software;Erasure DataStorage 4 Result;Erasure DataStorage 4 Certificate URL;Erasure DataStorage 4 Type;Erasure DataStorage 4 Method;Erasure DataStorage 4 Elapsed (hours);Erasure DataStorage 4 Date;Erasure DataStorage 4 Steps;Erasure DataStorage 4 Steps Start Time;Erasure DataStorage 4 Steps End Time;Benchmark DataStorage 4 Read Speed (MB/s);Benchmark DataStorage 4 Writing speed (MB/s);Test DataStorage 4 Software;Test DataStorage 4 Type;Test DataStorage 4 Result;Test DataStorage 4 Power on (hours used);Test DataStorage 4 Lifetime remaining (percentage);Motherboard 1;Motherboard 1 Manufacturer;Motherboard 1 Model;Motherboard 1 Serial Number;Display 1;Display 1 Manufacturer;Display 1 Model;Display 1 Serial Number;GraphicCard 1;GraphicCard 1 Manufacturer;GraphicCard 1 Model;GraphicCard 1 Serial Number;GraphicCard 1 Memory (MB);GraphicCard 2;GraphicCard 2 Manufacturer;GraphicCard 2 Model;GraphicCard 2 Serial Number;GraphicCard 2 Memory (MB);NetworkAdapter 1;NetworkAdapter 1 Manufacturer;NetworkAdapter 1 Model;NetworkAdapter 1 Serial Number;NetworkAdapter 2;NetworkAdapter 2 Manufacturer;NetworkAdapter 2 Model;NetworkAdapter 2 Serial Number;SoundCard 1;SoundCard 1 Manufacturer;SoundCard 1 Model;SoundCard 1 Serial Number;SoundCard 2;SoundCard 2 Manufacturer;SoundCard 2 Model;SoundCard 2 Serial Number;Device Rate;Device Range;Processor Rate;Processor Range;RAM Rate;RAM Range;Data Storage Rate;Data Storage Range;Price;Benchmark RamSysbench (points) -93652;;http://localhost/devices/93652;;;;;;;;;;;desktop-d1mr-d1ml-d1s;Desktop;Microtower;d1s;d1ml;d1mr;Mon Aug 2 10:25:31 2021;Workbench 11.0;2021-08-02 10:25:31.457986+02:00;;;;p1ml;0;0;Processor 6: model p1ml, S/N p1s;p1mr;p1ml;p1s;;1.6;2410.0;;;;;;;;;;RamModule 5: model rm1ml, S/N rm1s;rm1mr;rm1ml;rm1s;;1333;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GraphicCard 4: model gc1ml, S/N gc1s;gc1mr;gc1ml;gc1s;;;;;;;;;;;;;;;;;;;;;;;1.0;VERY_LOW;1.0;VERY_LOW;1.0;VERY_LOW;1.0;VERY_LOW;; +O48N2;;http://localhost/devices/O48N2;;named;O48N2;FooOrg;;;;;;;desktop-d1mr-d1ml-d1s;Desktop;Microtower;d1s;d1ml;d1mr;Mon Aug 2 10:25:31 2021;Workbench 11.0;2021-08-02 10:25:31.457986+02:00;;;;p1ml;0;0;Processor 6: model p1ml, S/N p1s;p1mr;p1ml;p1s;;1.6;2410.0;;;;;;;;;;RamModule 5: model rm1ml, S/N rm1s;rm1mr;rm1ml;rm1s;;1333;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GraphicCard 4: model gc1ml, S/N gc1s;gc1mr;gc1ml;gc1s;;;;;;;;;;;;;;;;;;;;;;;1.0;VERY_LOW;1.0;VERY_LOW;1.0;VERY_LOW;1.0;VERY_LOW;; diff --git a/tests/files/proposal_extended_csv_report.csv b/tests/files/proposal_extended_csv_report.csv index 2aacace8..67f8bf27 100644 --- a/tests/files/proposal_extended_csv_report.csv +++ b/tests/files/proposal_extended_csv_report.csv @@ -1,3 +1,3 @@ DHID;DocumentID;Public Link;Lots;Tag 1 Type;Tag 1 ID;Tag 1 Organization;Tag 2 Type;Tag 2 ID;Tag 2 Organization;Tag 3 Type;Tag 3 ID;Tag 3 Organization;Device Hardware ID;Device Type;Device Chassis;Device Serial Number;Device Model;Device Manufacturer;Registered in;Registered (process);Updated in (software);Updated in (web);Physical state;Trading state;Processor;RAM (MB);Data Storage Size (MB);Processor 1;Processor 1 Manufacturer;Processor 1 Model;Processor 1 Serial Number;Processor 1 Number of cores;Processor 1 Speed (GHz);Benchmark Processor 1 (points);Benchmark ProcessorSysbench Processor 1 (points);Processor 2;Processor 2 Manufacturer;Processor 2 Model;Processor 2 Serial Number;Processor 2 Number of cores;Processor 2 Speed (GHz);Benchmark Processor 2 (points);Benchmark ProcessorSysbench Processor 2 (points);RamModule 1;RamModule 1 Manufacturer;RamModule 1 Model;RamModule 1 Serial Number;RamModule 1 Size (MB);RamModule 1 Speed (MHz);RamModule 2;RamModule 2 Manufacturer;RamModule 2 Model;RamModule 2 Serial Number;RamModule 2 Size (MB);RamModule 2 Speed (MHz);RamModule 3;RamModule 3 Manufacturer;RamModule 3 Model;RamModule 3 Serial Number;RamModule 3 Size (MB);RamModule 3 Speed (MHz);RamModule 4;RamModule 4 Manufacturer;RamModule 4 Model;RamModule 4 Serial Number;RamModule 4 Size (MB);RamModule 4 Speed (MHz);DataStorage 1;DataStorage 1 Manufacturer;DataStorage 1 Model;DataStorage 1 Serial Number;DataStorage 1 Size (MB);Erasure DataStorage 1;Erasure DataStorage 1 Serial Number;Erasure DataStorage 1 Size (MB);Erasure DataStorage 1 Software;Erasure DataStorage 1 Result;Erasure DataStorage 1 Certificate URL;Erasure DataStorage 1 Type;Erasure DataStorage 1 Method;Erasure DataStorage 1 Elapsed (hours);Erasure DataStorage 1 Date;Erasure DataStorage 1 Steps;Erasure DataStorage 1 Steps Start Time;Erasure DataStorage 1 Steps End Time;Benchmark DataStorage 1 Read Speed (MB/s);Benchmark DataStorage 1 Writing speed (MB/s);Test DataStorage 1 Software;Test DataStorage 1 Type;Test DataStorage 1 Result;Test DataStorage 1 Power on (hours used);Test DataStorage 1 Lifetime remaining (percentage);DataStorage 2;DataStorage 2 Manufacturer;DataStorage 2 Model;DataStorage 2 Serial Number;DataStorage 2 Size (MB);Erasure DataStorage 2;Erasure DataStorage 2 Serial Number;Erasure DataStorage 2 Size (MB);Erasure DataStorage 2 Software;Erasure DataStorage 2 Result;Erasure DataStorage 2 Certificate URL;Erasure DataStorage 2 Type;Erasure DataStorage 2 Method;Erasure DataStorage 2 Elapsed (hours);Erasure DataStorage 2 Date;Erasure DataStorage 2 Steps;Erasure DataStorage 2 Steps Start Time;Erasure DataStorage 2 Steps End Time;Benchmark DataStorage 2 Read Speed (MB/s);Benchmark DataStorage 2 Writing speed (MB/s);Test DataStorage 2 Software;Test DataStorage 2 Type;Test DataStorage 2 Result;Test DataStorage 2 Power on (hours used);Test DataStorage 2 Lifetime remaining (percentage);DataStorage 3;DataStorage 3 Manufacturer;DataStorage 3 Model;DataStorage 3 Serial Number;DataStorage 3 Size (MB);Erasure DataStorage 3;Erasure DataStorage 3 Serial Number;Erasure DataStorage 3 Size (MB);Erasure DataStorage 3 Software;Erasure DataStorage 3 Result;Erasure DataStorage 3 Certificate URL;Erasure DataStorage 3 Type;Erasure DataStorage 3 Method;Erasure DataStorage 3 Elapsed (hours);Erasure DataStorage 3 Date;Erasure DataStorage 3 Steps;Erasure DataStorage 3 Steps Start Time;Erasure DataStorage 3 Steps End Time;Benchmark DataStorage 3 Read Speed (MB/s);Benchmark DataStorage 3 Writing speed (MB/s);Test DataStorage 3 Software;Test DataStorage 3 Type;Test DataStorage 3 Result;Test DataStorage 3 Power on (hours used);Test DataStorage 3 Lifetime remaining (percentage);DataStorage 4;DataStorage 4 Manufacturer;DataStorage 4 Model;DataStorage 4 Serial Number;DataStorage 4 Size (MB);Erasure DataStorage 4;Erasure DataStorage 4 Serial Number;Erasure DataStorage 4 Size (MB);Erasure DataStorage 4 Software;Erasure DataStorage 4 Result;Erasure DataStorage 4 Certificate URL;Erasure DataStorage 4 Type;Erasure DataStorage 4 Method;Erasure DataStorage 4 Elapsed (hours);Erasure DataStorage 4 Date;Erasure DataStorage 4 Steps;Erasure DataStorage 4 Steps Start Time;Erasure DataStorage 4 Steps End Time;Benchmark DataStorage 4 Read Speed (MB/s);Benchmark DataStorage 4 Writing speed (MB/s);Test DataStorage 4 Software;Test DataStorage 4 Type;Test DataStorage 4 Result;Test DataStorage 4 Power on (hours used);Test DataStorage 4 Lifetime remaining (percentage);Motherboard 1;Motherboard 1 Manufacturer;Motherboard 1 Model;Motherboard 1 Serial Number;Display 1;Display 1 Manufacturer;Display 1 Model;Display 1 Serial Number;GraphicCard 1;GraphicCard 1 Manufacturer;GraphicCard 1 Model;GraphicCard 1 Serial Number;GraphicCard 1 Memory (MB);GraphicCard 2;GraphicCard 2 Manufacturer;GraphicCard 2 Model;GraphicCard 2 Serial Number;GraphicCard 2 Memory (MB);NetworkAdapter 1;NetworkAdapter 1 Manufacturer;NetworkAdapter 1 Model;NetworkAdapter 1 Serial Number;NetworkAdapter 2;NetworkAdapter 2 Manufacturer;NetworkAdapter 2 Model;NetworkAdapter 2 Serial Number;SoundCard 1;SoundCard 1 Manufacturer;SoundCard 1 Model;SoundCard 1 Serial Number;SoundCard 2;SoundCard 2 Manufacturer;SoundCard 2 Model;SoundCard 2 Serial Number;Device Rate;Device Range;Processor Rate;Processor Range;RAM Rate;RAM Range;Data Storage Rate;Data Storage Range;Price;Benchmark RamSysbench (points) -93652;;http://localhost/devices/93652;;named;foo;FooOrg;;;;;;;laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b;Laptop;Netbook;b8oaas048285;1001pxd;asustek computer inc.;Mon Aug 2 10:27:27 2021;Workbench 11.0a2;2021-08-02 10:27:27.772331+02:00;;;;intel atom cpu n455 @ 2.66ghz;1024;238475;Processor 6: model intel atom cpu n455 @ 2.66ghz, S/N None;intel corp.;intel atom cpu n455 @ 2.66ghz;;1;2.667;6666.24;164.0803;;;;;;;;;RamModule 10: model None, S/N None;;;;1024;667;;;;;;;;;;;;;;;;;;;HardDrive 11: model hts54322, S/N e2024242cv86mm;hitachi;hts54322;e2024242cv86mm;238475;harddrive-hitachi-hts54322-e2024242cv86mm;e2024242cv86mm;238475;Workbench 11.0a2;Success;;EraseBasic;Shred;1:16:49;2021-08-02 10:27:27.163611+02:00;✓ – StepRandom 1:16:49;2018-07-03 11:15:22.257059+02:00;2018-07-03 12:32:11.843190+02:00;66.2;21.8;Workbench 11.0a2;Short;Failure;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Motherboard 12: model 1001pxd, S/N eee0123456720;asustek computer inc.;1001pxd;eee0123456720;;;;;GraphicCard 7: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None;intel corporation;atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller;;256;;;;;;NetworkAdapter 4: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9;qualcomm atheros;ar9285 wireless network adapter;74:2f:68:8b:fd:c9;NetworkAdapter 5: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b;qualcomm atheros;ar8152 v2.0 fast ethernet;14:da:e9:42:f6:7b;SoundCard 8: model nm10/ich7 family high definition audio controller, S/N None;intel corporation;nm10/ich7 family high definition audio controller;;;;;;1.75;LOW;1.55;LOW;1.53;LOW;3.76;HIGH;52.50 €;15.7188 -J2MA2;;http://localhost/devices/J2MA2;;;;;;;;;;;laptop-asustek_computer_inc-1001pxd-b8oaas048287-14:da:e9:42:f6:7c;Laptop;Netbook;b8oaas048287;1001pxd;asustek computer inc.;Mon Aug 2 10:27:27 2021;Workbench 11.0b11;2021-08-02 10:27:27.528832+02:00;;;;intel atom cpu n455 @ 1.66ghz;2048;558558;Processor 17: model intel atom cpu n455 @ 1.66ghz, S/N None;intel corp.;intel atom cpu n455 @ 1.66ghz;;1;1.667;6666.24;164.0803;;;;;;;;;RamModule 20: model None, S/N None;;;;1024;667;RamModule 21: model 48594d503131325336344350362d53362020, S/N 4f43487b;hynix semiconductor;48594d503131325336344350362d53362020;4f43487b;1024;667;;;;;;;;;;;;;HardDrive 22: model hts54322, S/N e2024242cv86hj;hitachi;hts54322;e2024242cv86hj;238475;harddrive-hitachi-hts54322-e2024242cv86hj;e2024242cv86hj;238475;Workbench 11.0b11;Success;;EraseBasic;Shred;1:16:49;2021-08-02 10:27:27.458348+02:00;✓ – StepRandom 1:16:49;2018-07-03 11:15:22.257059+02:00;2018-07-03 12:32:11.843190+02:00;66.2;21.8;Workbench 11.0b11;Extended;Failure;;;DataStorage 23: model wdc wd1600bevt-2, S/N wd-wx11a80w7430;western digital;wdc wd1600bevt-2;wd-wx11a80w7430;160041;datastorage-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430;wd-wx11a80w7430;160041;Workbench 11.0b11;Failure;;EraseBasic;Shred;0:45:36;2021-08-02 10:27:27.460573+02:00;✓ – StepRandom 0:45:36;2019-10-23 09:49:54.410830+02:00;2019-10-23 10:35:31.400587+02:00;41.6;17.3;Workbench 11.0b11;Short;Success;5293;195 days, 12:00:00;SolidStateDrive 24: model wdc wd1600bevt-2, S/N wd-wx11a80w7430;western digital;wdc wd1600bevt-2;wd-wx11a80w7430;160042;solidstatedrive-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430;wd-wx11a80w7430;160042;Workbench 11.0b11;Success;;EraseSectors;Badblocks;1:46:03;2021-08-02 10:27:27.464663+02:00;✓ – StepRandom 0:46:03,✓ – StepZero 1:00:00;2019-08-19 18:48:19.690458+02:00,2019-08-19 19:34:22.690458+02:00;2019-08-19 19:34:22.930562+02:00,2019-08-19 20:34:22.930562+02:00;41.1;17.1;Workbench 11.0b11;Short;Success;5231;194 days, 17:00:00;;;;;;;;;;;;;;;;;;;;;;;;;;Motherboard 25: model 1001pxd, S/N eee0123456789;asustek computer inc.;1001pxd;eee0123456789;;"auo ""auo""";auo lcd monitor;;GraphicCard 18: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None;intel corporation;atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller;;256;;;;;;NetworkAdapter 15: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8;qualcomm atheros;ar9285 wireless network adapter;74:2f:68:8b:fd:c8;NetworkAdapter 16: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c;qualcomm atheros;ar8152 v2.0 fast ethernet;14:da:e9:42:f6:7c;SoundCard 9: model usb 2.0 uvc vga webcam, S/N 0x0001;azurewave;usb 2.0 uvc vga webcam;0x0001;SoundCard 19: model nm10/ich7 family high definition audio controller, S/N None;intel corporation;nm10/ich7 family high definition audio controller;;1.72;LOW;1.31;LOW;1.99;LOW;3.97;HIGH;51.60 €;15.7188 +O48N2;;http://localhost/devices/O48N2;;named;O48N2;FooOrg;;;;;;;laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b;Laptop;Netbook;b8oaas048285;1001pxd;asustek computer inc.;Mon Aug 2 10:27:27 2021;Workbench 11.0a2;2021-08-02 10:27:27.772331+02:00;;;;intel atom cpu n455 @ 2.66ghz;1024;238475;Processor 6: model intel atom cpu n455 @ 2.66ghz, S/N None;intel corp.;intel atom cpu n455 @ 2.66ghz;;1;2.667;6666.24;164.0803;;;;;;;;;RamModule 10: model None, S/N None;;;;1024;667;;;;;;;;;;;;;;;;;;;HardDrive 11: model hts54322, S/N e2024242cv86mm;hitachi;hts54322;e2024242cv86mm;238475;harddrive-hitachi-hts54322-e2024242cv86mm;e2024242cv86mm;238475;Workbench 11.0a2;Success;;EraseBasic;Shred;1:16:49;2021-08-02 10:27:27.163611+02:00;✓ – StepRandom 1:16:49;2018-07-03 11:15:22.257059+02:00;2018-07-03 12:32:11.843190+02:00;66.2;21.8;Workbench 11.0a2;Short;Failure;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Motherboard 12: model 1001pxd, S/N eee0123456720;asustek computer inc.;1001pxd;eee0123456720;;;;;GraphicCard 7: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None;intel corporation;atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller;;256;;;;;;NetworkAdapter 4: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9;qualcomm atheros;ar9285 wireless network adapter;74:2f:68:8b:fd:c9;NetworkAdapter 5: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b;qualcomm atheros;ar8152 v2.0 fast ethernet;14:da:e9:42:f6:7b;SoundCard 8: model nm10/ich7 family high definition audio controller, S/N None;intel corporation;nm10/ich7 family high definition audio controller;;;;;;1.75;LOW;1.55;LOW;1.53;LOW;3.76;HIGH;52.50 €;15.7188 +J2MA2;;http://localhost/devices/J2MA2;;named;J2MA2;FooOrg;;;;;;;laptop-asustek_computer_inc-1001pxd-b8oaas048287-14:da:e9:42:f6:7c;Laptop;Netbook;b8oaas048287;1001pxd;asustek computer inc.;Mon Aug 2 10:27:27 2021;Workbench 11.0b11;2021-08-02 10:27:27.528832+02:00;;;;intel atom cpu n455 @ 1.66ghz;2048;558558;Processor 17: model intel atom cpu n455 @ 1.66ghz, S/N None;intel corp.;intel atom cpu n455 @ 1.66ghz;;1;1.667;6666.24;164.0803;;;;;;;;;RamModule 20: model None, S/N None;;;;1024;667;RamModule 21: model 48594d503131325336344350362d53362020, S/N 4f43487b;hynix semiconductor;48594d503131325336344350362d53362020;4f43487b;1024;667;;;;;;;;;;;;;HardDrive 22: model hts54322, S/N e2024242cv86hj;hitachi;hts54322;e2024242cv86hj;238475;harddrive-hitachi-hts54322-e2024242cv86hj;e2024242cv86hj;238475;Workbench 11.0b11;Success;;EraseBasic;Shred;1:16:49;2021-08-02 10:27:27.458348+02:00;✓ – StepRandom 1:16:49;2018-07-03 11:15:22.257059+02:00;2018-07-03 12:32:11.843190+02:00;66.2;21.8;Workbench 11.0b11;Extended;Failure;;;DataStorage 23: model wdc wd1600bevt-2, S/N wd-wx11a80w7430;western digital;wdc wd1600bevt-2;wd-wx11a80w7430;160041;datastorage-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430;wd-wx11a80w7430;160041;Workbench 11.0b11;Failure;;EraseBasic;Shred;0:45:36;2021-08-02 10:27:27.460573+02:00;✓ – StepRandom 0:45:36;2019-10-23 09:49:54.410830+02:00;2019-10-23 10:35:31.400587+02:00;41.6;17.3;Workbench 11.0b11;Short;Success;5293;195 days, 12:00:00;SolidStateDrive 24: model wdc wd1600bevt-2, S/N wd-wx11a80w7430;western digital;wdc wd1600bevt-2;wd-wx11a80w7430;160042;solidstatedrive-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430;wd-wx11a80w7430;160042;Workbench 11.0b11;Success;;EraseSectors;Badblocks;1:46:03;2021-08-02 10:27:27.464663+02:00;✓ – StepRandom 0:46:03,✓ – StepZero 1:00:00;2019-08-19 18:48:19.690458+02:00,2019-08-19 19:34:22.690458+02:00;2019-08-19 19:34:22.930562+02:00,2019-08-19 20:34:22.930562+02:00;41.1;17.1;Workbench 11.0b11;Short;Success;5231;194 days, 17:00:00;;;;;;;;;;;;;;;;;;;;;;;;;;Motherboard 25: model 1001pxd, S/N eee0123456789;asustek computer inc.;1001pxd;eee0123456789;;"auo ""auo""";auo lcd monitor;;GraphicCard 18: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None;intel corporation;atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller;;256;;;;;;NetworkAdapter 15: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8;qualcomm atheros;ar9285 wireless network adapter;74:2f:68:8b:fd:c8;NetworkAdapter 16: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c;qualcomm atheros;ar8152 v2.0 fast ethernet;14:da:e9:42:f6:7c;SoundCard 9: model usb 2.0 uvc vga webcam, S/N 0x0001;azurewave;usb 2.0 uvc vga webcam;0x0001;SoundCard 19: model nm10/ich7 family high definition audio controller, S/N None;intel corporation;nm10/ich7 family high definition audio controller;;1.72;LOW;1.31;LOW;1.99;LOW;3.97;HIGH;51.60 €;15.7188 diff --git a/tests/test_action.py b/tests/test_action.py index f5b88068..d8216ab7 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -260,10 +260,10 @@ def test_generic_action(action_model_state: Tuple[models.Action, states.Trading] @pytest.mark.parametrize('action_model', (pytest.param(ams, id=ams.__class__.__name__) for ams in [ - models.Recycling, - models.Use, - models.Refurbish, - models.Management + models.Recycling, + models.Use, + models.Refurbish, + models.Management ])) def test_simple_status_actions(action_model: models.Action, user: UserClient, user2: UserClient): """Simple test of status action.""" @@ -281,10 +281,10 @@ def test_simple_status_actions(action_model: models.Action, user: UserClient, us @pytest.mark.parametrize('action_model', (pytest.param(ams, id=ams.__class__.__name__) for ams in [ - models.Recycling, - models.Use, - models.Refurbish, - models.Management + models.Recycling, + models.Use, + models.Refurbish, + models.Management ])) def test_outgoinlot_status_actions(action_model: models.Action, user: UserClient, user2: UserClient): """Test of status actions in outgoinlot.""" @@ -328,7 +328,7 @@ def test_outgoinlot_status_actions(action_model: models.Action, user: UserClient assert device['actions'][-1]['id'] == action['id'] assert action['author']['id'] == user.user['id'] - assert action['rol_user']['id'] == user.user['id'] + assert action['rol_user']['id'] == user2.user['id'] @pytest.mark.mvp @@ -386,14 +386,14 @@ def test_history_status_actions(user: UserClient, user2: UserClient): assert action['id'] == str(device.status.id) assert device.status.t == models.Recycling.t assert [action['id']] == [str(ac.id) for ac in device.history_status] - + # Case 2 action2 = {'type': models.Refurbish.t, 'devices': [device.id]} action2, _ = user.post(action2, res=models.Action) assert action2['id'] == str(device.status.id) assert device.status.t == models.Refurbish.t assert [action2['id']] == [str(ac.id) for ac in device.history_status] - + # Case 3 lot, _ = user.post({'name': 'MyLot'}, res=Lot) user.post({}, @@ -1396,6 +1396,7 @@ def test_confirm_revoke(user: UserClient, user2: UserClient): user.post(res=models.Action, data=request_post) trade = models.Trade.query.one() + device = trade.devices[0] request_confirm = { 'type': 'Confirm', @@ -1416,9 +1417,10 @@ def test_confirm_revoke(user: UserClient, user2: UserClient): # Normal revoke user2.post(res=models.Action, data=request_revoke) - # 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 + # You can to do one confirmation next of one revoke + user2.post(res=models.Action, data=request_confirm) + assert len(trade.acceptances) == 4 + assert device.trading(trade.lot) == "TradeConfirmed" @pytest.mark.mvp @@ -1455,7 +1457,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 SCRAP to confirm it request_post = { 'type': 'Trade', @@ -1516,9 +1518,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 'type': 'Confirm', 'action': trade.id, 'devices': [ - snap1['device']['id'], - snap2['device']['id'], - snap3['device']['id'], snap4['device']['id'], snap5['device']['id'], snap6['device']['id'], @@ -1535,7 +1534,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({}, @@ -1554,31 +1553,28 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): # the SCRAP confirms the revoke action request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, + 'type': 'Revoke', + 'action': trade.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[-1].t == 'Revoke' assert device_10.actions[-2].t == 'Revoke' # assert len(trade.lot.devices) == len(trade.devices) == 9 # assert not device_10 in trade.devices # check validation error request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device_10.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [ snap9['device']['id'] ] } - 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({}, @@ -1604,7 +1600,7 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient): 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 device_10.actions[-3].t == 'Revoke' # assert len(device_10.actions) == 13 @@ -1756,7 +1752,7 @@ def test_trade_case1(user: UserClient, user2: UserClient): item='{}/devices'.format(lot['id']), query=devices[:-1]) - # 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', @@ -1772,31 +1768,23 @@ def test_trade_case1(user: UserClient, user2: UserClient): user.post(res=models.Action, data=request_post) trade = models.Trade.query.one() - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices[-1:]) + lot = trade.lot + device = trade.devices[0] - device1, device2 = trade.devices + assert device.actions[-2].t == 'Trade' + assert device.actions[-1].t == 'Confirm' + assert device.actions[-1].user == trade.user_to - assert device1.actions[-2].t == 'Trade' - assert device1.actions[-1].t == 'Confirm' - assert device1.actions[-1].user == trade.user_to - assert device2.actions[-2].t == 'Trade' - assert device2.actions[-1].t == 'Confirm' - assert device2.actions[-1].user == trade.user_to + user.delete({}, + res=Lot, + item='{}/devices'.format(lot.id), + query=devices[:-1], status=200) - lot, _ = user.delete({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices, status=200) - - assert device1.actions[-2].t == 'Revoke' - assert device1.actions[-1].t == 'ConfirmRevoke' - assert device1.actions[-1].user == trade.user_to - assert device2.actions[-2].t == 'Revoke' - assert device2.actions[-1].t == 'ConfirmRevoke' - assert device2.actions[-1].user == trade.user_to + assert device not in trade.lot.devices + assert device.trading(trade.lot) == 'RevokeConfirmed' + assert device.actions[-2].t == 'Confirm' + assert device.actions[-1].t == 'Revoke' + assert device.actions[-1].user == trade.user_to @pytest.mark.mvp @@ -1855,12 +1843,13 @@ def test_trade_case2(user: UserClient, user2: UserClient): # Normal revoke user.post(res=models.Action, data=request_revoke) - assert device1.actions[-2].t == 'Revoke' - assert device1.actions[-1].t == 'ConfirmRevoke' + assert device1.actions[-2].t == 'Confirm' + assert device1.actions[-1].t == 'Revoke' assert device1.actions[-1].user == trade.user_to - assert device2.actions[-2].t == 'Revoke' - assert device2.actions[-1].t == 'ConfirmRevoke' + assert device2.actions[-2].t == 'Confirm' + assert device2.actions[-1].t == 'Revoke' assert device2.actions[-1].user == trade.user_to + assert device1.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -1868,7 +1857,6 @@ def test_trade_case2(user: UserClient, user2: UserClient): def test_trade_case3(user: UserClient, user2: UserClient): # 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, _ = user2.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) @@ -1880,7 +1868,7 @@ def test_trade_case3(user: UserClient, user2: UserClient): item='{}/devices'.format(lot['id']), query=devices[:-1]) - # 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', @@ -1915,9 +1903,10 @@ def test_trade_case3(user: UserClient, user2: UserClient): item='{}/devices'.format(lot['id']), query=devices[-1:], status=200) - assert device2.actions[-2].t == 'Revoke' - assert device2.actions[-1].t == 'ConfirmRevoke' + assert device2.actions[-2].t == 'Confirm' + assert device2.actions[-1].t == 'Revoke' assert device2.actions[-1].user == trade.user_from + assert device2.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -1979,9 +1968,10 @@ def test_trade_case4(user: UserClient, user2: UserClient): assert device1.actions[-2].t == 'Trade' assert device1.actions[-1].t == 'Confirm' assert device1.actions[-1].user == trade.user_to - assert device2.actions[-2].t == 'Revoke' - assert device2.actions[-1].t == 'ConfirmRevoke' + assert device2.actions[-2].t == 'Confirm' + assert device2.actions[-1].t == 'Revoke' assert device2.actions[-1].user == trade.user_from + assert device2.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2036,8 +2026,8 @@ def test_trade_case5(user: UserClient, user2: UserClient): assert device2.actions[-1].user == trade.user_from request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device2.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device2.id], } @@ -2045,8 +2035,9 @@ def test_trade_case5(user: UserClient, user2: UserClient): user.post(res=models.Action, data=request_confirm_revoke) assert device2.actions[-2].t == 'Revoke' - assert device2.actions[-1].t == 'ConfirmRevoke' + assert device2.actions[-1].t == 'Revoke' assert device2.actions[-1].user == trade.user_to + assert device2.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2106,8 +2097,8 @@ def test_trade_case6(user: UserClient, user2: UserClient): assert device2.actions[-1].user == trade.user_to request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device2.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device2.id], } @@ -2115,8 +2106,9 @@ def test_trade_case6(user: UserClient, user2: UserClient): user2.post(res=models.Action, data=request_confirm_revoke) assert device2.actions[-2].t == 'Revoke' - assert device2.actions[-1].t == 'ConfirmRevoke' + assert device2.actions[-1].t == 'Revoke' assert device2.actions[-1].user == trade.user_from + assert device2.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2158,6 +2150,7 @@ def test_trade_case7(user: UserClient, user2: UserClient): # Normal revoke user2.post(res=models.Action, data=request_confirm) + assert device.trading(trade.lot) == 'TradeConfirmed' lot, _ = user.delete({}, res=Lot, @@ -2165,14 +2158,14 @@ def test_trade_case7(user: UserClient, user2: UserClient): query=devices, status=200) request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user2.post(res=models.Action, data=request_confirm_revoke) - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_from assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_to @@ -2182,6 +2175,7 @@ def test_trade_case7(user: UserClient, user2: UserClient): assert device.actions[-4].user == trade.user_to assert device.actions[-5].t == 'Trade' assert device.actions[-5].author == trade.user_to + assert device.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2223,6 +2217,7 @@ def test_trade_case8(user: UserClient, user2: UserClient): # Normal revoke user2.post(res=models.Action, data=request_confirm) + assert device.trading(trade.lot) == 'TradeConfirmed' request_revoke = { 'type': 'Revoke', @@ -2234,14 +2229,14 @@ def test_trade_case8(user: UserClient, user2: UserClient): user.post(res=models.Action, data=request_revoke) request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user2.post(res=models.Action, data=request_confirm_revoke) - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_from assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_to @@ -2251,6 +2246,7 @@ def test_trade_case8(user: UserClient, user2: UserClient): assert device.actions[-4].user == trade.user_to assert device.actions[-5].t == 'Trade' assert device.actions[-5].author == trade.user_to + assert device.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2303,6 +2299,7 @@ def test_trade_case9(user: UserClient, user2: UserClient): # Normal revoke user.post(res=models.Action, data=request_confirm) + assert device.trading(trade.lot) == 'TradeConfirmed' assert device.owner == trade.user_to @@ -2312,8 +2309,8 @@ def test_trade_case9(user: UserClient, user2: UserClient): query=devices[-1:], status=200) request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } @@ -2321,7 +2318,7 @@ def test_trade_case9(user: UserClient, user2: UserClient): assert device.owner == trade.user_from - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_to assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_from @@ -2331,6 +2328,7 @@ def test_trade_case9(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 + assert device.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2374,6 +2372,7 @@ def test_trade_case10(user: UserClient, user2: UserClient): device1, device = trade.devices assert device.owner == trade.user_from + # assert device.trading(trade.lot) == 'Confirm' request_confirm = { 'type': 'Confirm', @@ -2383,6 +2382,7 @@ def test_trade_case10(user: UserClient, user2: UserClient): # Normal confirm user.post(res=models.Action, data=request_confirm) + # assert device.trading(trade.lot) == 'TradeConfirmed' assert device.owner == trade.user_to @@ -2394,18 +2394,18 @@ def test_trade_case10(user: UserClient, user2: UserClient): # Normal revoke user2.post(res=models.Action, data=request_revoke) + assert device.trading(trade.lot) == 'Revoke' request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user.post(res=models.Action, data=request_confirm_revoke) assert device.owner == trade.user_from - - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_to assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_from @@ -2415,6 +2415,7 @@ def test_trade_case10(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 + assert device.trading(trade.lot) == 'RevokeConfirmed' @pytest.mark.mvp @@ -2451,6 +2452,7 @@ def test_trade_case11(user: UserClient, user2: UserClient): trade = models.Trade.query.one() device1, device = trade.devices + assert device.trading(trade.lot) == 'Confirm' request_confirm = { 'type': 'Confirm', @@ -2459,21 +2461,24 @@ def test_trade_case11(user: UserClient, user2: UserClient): } user2.post(res=models.Action, data=request_confirm) + assert device.trading(trade.lot) == 'TradeConfirmed' lot, _ = user2.delete({}, res=Lot, item='{}/devices'.format(lot['id']), query=devices[-1:], status=200) + assert device.trading(trade.lot) == 'Revoke' request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user.post(res=models.Action, data=request_confirm_revoke) + assert device.trading(trade.lot) == 'RevokeConfirmed' - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_to assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_from @@ -2519,6 +2524,7 @@ def test_trade_case12(user: UserClient, user2: UserClient): trade = models.Trade.query.one() device1, device = trade.devices + assert device.trading(trade.lot) == 'Confirm' # Normal confirm request_confirm = { @@ -2528,6 +2534,7 @@ def test_trade_case12(user: UserClient, user2: UserClient): } user2.post(res=models.Action, data=request_confirm) + assert device.trading(trade.lot) == 'TradeConfirmed' request_revoke = { 'type': 'Revoke', @@ -2537,16 +2544,18 @@ def test_trade_case12(user: UserClient, user2: UserClient): # Normal revoke user2.post(res=models.Action, data=request_revoke) + assert device.trading(trade.lot) == 'Revoke' request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user.post(res=models.Action, data=request_confirm_revoke) + assert device.trading(trade.lot) == 'RevokeConfirmed' - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_to assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_from @@ -2597,6 +2606,8 @@ def test_trade_case13(user: UserClient, user2: UserClient): query=devices[-1:]) device1, device = trade.devices + assert device1.trading(trade.lot) == 'NeedConfirmation' + assert device.trading(trade.lot) == 'Confirm' request_confirm = { 'type': 'Confirm', @@ -2605,21 +2616,26 @@ def test_trade_case13(user: UserClient, user2: UserClient): } user.post(res=models.Action, data=request_confirm) + assert device1.trading(trade.lot) == 'Confirm' + assert device.trading(trade.lot) == 'TradeConfirmed' lot, _ = user.delete({}, res=Lot, item='{}/devices'.format(lot['id']), query=devices[-1:], status=200) + assert device1.trading(trade.lot) == 'Confirm' + assert device.trading(trade.lot) == 'Revoke' request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user2.post(res=models.Action, data=request_confirm_revoke) + assert device.trading(trade.lot) == 'RevokeConfirmed' - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_from assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_to @@ -2670,6 +2686,8 @@ def test_trade_case14(user: UserClient, user2: UserClient): query=devices[-1:]) device1, device = trade.devices + assert device1.trading(trade.lot) == 'NeedConfirmation' + assert device.trading(trade.lot) == 'Confirm' # Normal confirm request_confirm = { @@ -2679,6 +2697,7 @@ def test_trade_case14(user: UserClient, user2: UserClient): } user.post(res=models.Action, data=request_confirm) + assert device.trading(trade.lot) == 'TradeConfirmed' request_revoke = { 'type': 'Revoke', @@ -2688,16 +2707,18 @@ def test_trade_case14(user: UserClient, user2: UserClient): # Normal revoke user.post(res=models.Action, data=request_revoke) + assert device.trading(trade.lot) == 'Revoke' request_confirm_revoke = { - 'type': 'ConfirmRevoke', - 'action': device.actions[-1].id, + 'type': 'Revoke', + 'action': trade.id, 'devices': [device.id], } user2.post(res=models.Action, data=request_confirm_revoke) + assert device.trading(trade.lot) == 'RevokeConfirmed' - assert device.actions[-1].t == 'ConfirmRevoke' + assert device.actions[-1].t == 'Revoke' assert device.actions[-1].user == trade.user_from assert device.actions[-2].t == 'Revoke' assert device.actions[-2].user == trade.user_to @@ -2718,7 +2739,7 @@ def test_action_web_erase(user: UserClient, client: Client): hash3 = hashlib.sha3_256(bfile.read()).hexdigest() snap, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) request = {'type': 'DataWipe', 'devices': [snap['device']['id']], 'name': 'borrado universal', 'severity': 'Info', 'description': 'nada que describir', 'url': 'http://www.google.com/', 'documentId': '33', 'endTime': '2021-07-07T22:00:00.000Z', 'filename': 'Certificado de borrado1.pdf', 'hash': hash3, 'success': 1, 'software': "Blanco"} - + user.post(res=models.Action, data=request) action = models.DataWipe.query.one() for dev in action.devices: diff --git a/tests/test_device.py b/tests/test_device.py index 9dcfd53f..7ea400a0 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -309,9 +309,10 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str): # they are not the same tags though # tag is a transient obj and db_tag the one from the db # they have the same pk though - assert tag != db_tag, 'They are not the same tags though' - assert db_tag.id == tag.id assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db' + assert tag != db_tag, 'They are not the same tags though' + for tag in pc.tags: + assert tag.id in ['foo', pc.devicehub_id] @pytest.mark.mvp @@ -346,8 +347,9 @@ def test_sync_execute_register_tag_linked_same_device(): pc.tags.add(Tag(id='foo')) db_pc = Sync().execute_register(pc) assert db_pc.id == orig_pc.id - assert len(db_pc.tags) == 1 - assert next(iter(db_pc.tags)).id == 'foo' + assert len(db_pc.tags) == 2 + for tag in db_pc.tags: + assert tag.id in ['foo', db_pc.devicehub_id] @pytest.mark.mvp @@ -399,13 +401,14 @@ def test_sync_execute_register_mismatch_between_tags_and_hid(): @pytest.mark.usefixtures(conftest.app_context.__name__) def test_get_device(user: UserClient): """Checks GETting a d.Desktop with its components.""" + g.user = User.query.one() pc = d.Desktop(model='p1mo', manufacturer='p1ma', serial_number='p1s', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc.components = OrderedSet([ - d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s', + d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s', owner_id=user.user['id']), d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']) ]) @@ -437,6 +440,7 @@ def test_get_device(user: UserClient): @pytest.mark.usefixtures(conftest.app_context.__name__) def test_get_devices(app: Devicehub, user: UserClient): """Checks GETting multiple devices.""" + g.user = User.query.one() pc = d.Desktop(model='p1mo', manufacturer='p1ma', serial_number='p1s', @@ -502,7 +506,8 @@ def test_get_devices_permissions(app: Devicehub, user: UserClient, user2: UserCl @pytest.mark.mvp -def test_get_devices_unassigned(app: Devicehub, user: UserClient): +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_get_devices_unassigned(user: UserClient): """Checks GETting multiple devices.""" user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) @@ -525,7 +530,8 @@ def test_get_devices_unassigned(app: Devicehub, user: UserClient): res=Lot, item='{}/devices'.format(my_lot['id']), query=[('id', device_id)]) - assert lot['devices'][0]['id'] == device_id, 'Lot contains device' + lot = Lot.query.filter_by(id=lot['id']).one() + assert next(iter(lot.devices)).id == device_id url = '/devices/?filter={"type":["Computer"]}&unassign=0' @@ -604,6 +610,7 @@ def test_device_public(user: UserClient, client: Client): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_computer_accessory_model(user: UserClient): + g.user = User.query.one() sai = d.SAI(owner_id=user.user['id']) db.session.add(sai) keyboard = d.Keyboard(layout=Layouts.ES, owner_id=user.user['id']) @@ -616,6 +623,7 @@ def test_computer_accessory_model(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_networking_model(user: UserClient): + g.user = User.query.one() router = d.Router(speed=1000, wireless=True, owner_id=user.user['id']) db.session.add(router) switch = d.Switch(speed=1000, wireless=False, owner_id=user.user['id']) diff --git a/tests/test_device_find.py b/tests/test_device_find.py index 306dae32..7407f3f8 100644 --- a/tests/test_device_find.py +++ b/tests/test_device_find.py @@ -183,7 +183,7 @@ def test_device_query(user: UserClient): pc = next(d for d in i['items'] if d['type'] == 'Desktop') assert len(pc['actions']) == 4 assert len(pc['components']) == 3 - assert not pc['tags'] + assert pc['tags'][0]['id'] == pc['devicehubID'] @pytest.mark.mvp @@ -201,10 +201,10 @@ def test_device_query_permitions(user: UserClient, user2: UserClient): user2.post(json_encode(basic_snapshot), res=Snapshot) i2, _ = user2.get(res=Device) pc2 = next(d for d in i2['items'] if d['type'] == 'Desktop') - + assert pc1['id'] != pc2['id'] assert pc1['hid'] == pc2['hid'] - + @pytest.mark.mvp def test_device_search_all_devices_token_if_empty(app: Devicehub, user: UserClient): diff --git a/tests/test_documents.py b/tests/test_documents.py index 240fa738..9e86acfe 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -219,7 +219,7 @@ def test_export_basic_snapshot(user: UserClient): item='devices/', accept='text/csv', query=[('filter', {'type': ['Computer']})]) - + f = StringIO(csv_str) obj_csv = csv.reader(f, f, delimiter=';', quotechar='"') export_csv = list(obj_csv) @@ -251,13 +251,13 @@ def test_check_insert_hash(app: Devicehub, user: UserClient, client: Client): assert ReportHash.query.filter_by(hash3=hash3).count() == 1 result, status = client.get(res=documents.DocumentDef.t, item='check/', query=[('hash', hash3)]) assert status.status_code == 200 - assert result == True + assert result ff = open('/tmp/test.csv', 'w') ff.write(csv_str) ff.close() - a= open('/tmp/test.csv').read() + a = open('/tmp/test.csv').read() assert hash3 == hashlib.sha3_256(a.encode('utf-8')).hexdigest() @@ -268,10 +268,7 @@ def test_export_extended(app: Devicehub, user: UserClient): snapshot2, _ = user.post(file('complete.export.snapshot'), res=Snapshot, status=201) with app.app_context(): # Create a pc with a tag - tag = Tag(id='foo', owner_id=user.user['id']) - # pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc = d.Device.query.filter_by(id=snapshot1['device']['id']).first() - pc.tags.add(tag) db.session.add(pc) db.session.commit() diff --git a/tests/test_lot.py b/tests/test_lot.py index e71f9d6d..44889825 100644 --- a/tests/test_lot.py +++ b/tests/test_lot.py @@ -313,7 +313,6 @@ def test_post_get_lot(user: UserClient): assert l['name'] == 'Foo' l, _ = user.get(res=Lot, item=l['id']) assert l['name'] == 'Foo' - assert not l['children'] def test_lot_post_add_children_view_ui_tree_normal(user: UserClient): @@ -355,45 +354,50 @@ def test_lot_post_add_children_view_ui_tree_normal(user: UserClient): @pytest.mark.mvp -def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient): +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_lot_post_add_remove_device_view(user: UserClient): """Tests adding a device to a lot using POST and removing it with DELETE. """ # todo check with components - with app.app_context(): - device = Desktop(serial_number='foo', - model='bar', - manufacturer='foobar', - chassis=ComputerChassis.Lunchbox, - owner_id=user.user['id']) - db.session.add(device) - db.session.commit() - device_id = device.id - devicehub_id = device.devicehub_id + g.user = User.query.one() + device = Desktop(serial_number='foo', + model='bar', + manufacturer='foobar', + chassis=ComputerChassis.Lunchbox, + owner_id=user.user['id']) + db.session.add(device) + db.session.commit() + device_id = device.id + devicehub_id = device.devicehub_id parent, _ = user.post(({'name': 'lot'}), res=Lot) lot, _ = user.post({}, res=Lot, item='{}/devices'.format(parent['id']), query=[('id', device_id)]) - assert lot['devices'][0]['id'] == device_id, 'Lot contains device' - device, _ = user.get(res=Device, item=devicehub_id) - assert len(device['lots']) == 1 - assert device['lots'][0]['id'] == lot['id'], 'Device is inside lot' + lot = Lot.query.filter_by(id=lot['id']).one() + assert list(lot.devices)[0].id == device_id, 'Lot contains device' + device = Device.query.filter_by(devicehub_id=devicehub_id).one() + assert len(device.lots) == 1 + # assert device['lots'][0]['id'] == lot['id'], 'Device is inside lot' + assert list(device.lots)[0].id == lot.id, 'Device is inside lot' # Remove the device - lot, _ = user.delete(res=Lot, - item='{}/devices'.format(parent['id']), - query=[('id', device_id)], - status=200) - assert not len(lot['devices']) + user.delete(res=Lot, + item='{}/devices'.format(parent['id']), + query=[('id', device_id)], + status=200) + assert not len(lot.devices) @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_lot_error_add_device_from_other_user(user: UserClient): + # TODO """Tests adding a device to a lot using POST and removing it with DELETE. """ + g.user = User.query.one() user2 = User(email='baz@baz.cxm', password='baz') user2.individuals.add(Person(name='Tommy')) db.session.add(user2) @@ -413,8 +417,9 @@ def test_lot_error_add_device_from_other_user(user: UserClient): res=Lot, item='{}/devices'.format(parent['id']), query=[('id', device_id)]) - assert lot['devices'] == [], 'Lot contains device' - assert len(lot['devices']) == 0 + lot = Lot.query.filter_by(id=lot['id']).one() + assert list(lot.devices) == [], 'Lot contains device' + assert len(lot.devices) == 0 @pytest.mark.mvp diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 54544328..cab985d1 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -2,6 +2,9 @@ import pytest from ereuse_devicehub.client import UserClient from ereuse_devicehub.resources.action import models as ma +from ereuse_devicehub.resources.documents import documents +from ereuse_devicehub.resources.lot.models import Lot +from ereuse_devicehub.resources.tradedocument.models import TradeDocument from tests import conftest from tests.conftest import file, yaml2json, json_encode @@ -20,8 +23,7 @@ def test_simple_metrics(user: UserClient): "finalUserCode": "abcdefjhi", "devices": [device_id], "description": "aaa", "startTime": "2020-11-01T02:00:00+00:00", - "endTime": "2020-12-01T02:00:00+00:00" - } + "endTime": "2020-12-01T02:00:00+00:00"} # Create Allocate user.post(res=ma.Allocate, data=post_request) @@ -65,8 +67,7 @@ def test_second_hdd_metrics(user: UserClient): "finalUserCode": "abcdefjhi", "devices": [device_id], "description": "aaa", "startTime": "2020-11-01T02:00:00+00:00", - "endTime": "2020-12-01T02:00:00+00:00" - } + "endTime": "2020-12-01T02:00:00+00:00"} # Create Allocate user.post(res=ma.Allocate, data=post_request) @@ -109,8 +110,7 @@ def test_metrics_with_live_null(user: UserClient): "finalUserCode": "abcdefjhi", "devices": [device_id], "description": "aaa", "startTime": "2020-11-01T02:00:00+00:00", - "endTime": "2020-12-01T02:00:00+00:00" - } + "endTime": "2020-12-01T02:00:00+00:00"} # Create Allocate user.post(res=ma.Allocate, data=post_request) @@ -120,3 +120,240 @@ def test_metrics_with_live_null(user: UserClient): res, _ = user.get("/metrics/") assert res == metrics + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_metrics_action_status(user: UserClient, user2: UserClient): + """ Checks one standard query of metrics.""" + # Insert computer + lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') + snap, _ = user.post(json_encode(lenovo), res=ma.Snapshot) + action = {'type': ma.Use.t, 'devices': [snap['device']['id']]} + action_use, _ = user.post(action, res=ma.Action) + csv_str, _ = user.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + head = 'DHID;Hid;Document-Name;Action-Type;Action-User-LastOwner-Supplier;Action-User-LastOwner-Receiver;Action-Create-By;Trade-Confirmed;Status-Created-By-Supplier-About-Reciber;Status-Receiver;Status Supplier – Created Date;Status Receiver – Created Date;Trade-Weight;Action-Create;Allocate-Start;Allocate-User-Code;Allocate-NumUsers;UsageTimeAllocate;Type;LiveCreate;UsageTimeHdd\n' + body = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Status;;foo@foo.com;Receiver;;;Use;;' + assert head in csv_str + assert body in csv_str + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): + """ Checks one standard query of metrics in a trade enviroment.""" + # Insert computer + lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') + acer = yaml2json('acer.happy.battery.snapshot') + snap1, _ = user.post(json_encode(lenovo), res=ma.Snapshot) + snap2, _ = user.post(json_encode(acer), res=ma.Snapshot) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + devices = [('id', snap1['device']['id']), + ('id', snap2['device']['id'])] + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices) + + action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} + user.post(action, res=ma.Action) + + request_post = { + 'type': 'Trade', + 'devices': [snap1['device']['id'], snap2['device']['id']], + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=ma.Action, data=request_post) + + action = {'type': ma.Use.t, 'devices': [snap1['device']['id']]} + action_use, _ = user.post(action, res=ma.Action) + csv_str, _ = user.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + + body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;' + body1_lenovo += 'foo2@foo.com;Supplier;NeedConfirmation;Use;;' + body2_lenovo = ';;0;0;Trade;0;0\n' + + body1_acer = 'J2MA2;laptop-acer-aohappy-lusea0d010038879a01601-00:26:c7:8e:cb:8c;;Trade;' + body1_acer += 'foo@foo.com;foo2@foo.com;Supplier;NeedConfirmation;;;;;0;' + body2_acer = ';;0;0;Trade;0;4692.0\n' + + assert body1_lenovo in csv_str + assert body2_lenovo in csv_str + assert body1_acer in csv_str + assert body2_acer in csv_str + + # User2 mark this device as Refurbish + action = {'type': ma.Use.t, 'devices': [snap1['device']['id']]} + action_use2, _ = user2.post(action, res=ma.Action) + csv_str, _ = user.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + + body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;' + body1_lenovo += 'foo2@foo.com;Supplier;NeedConfirmation;Use;Use;' + body2_lenovo = ';;0;0;Trade;0;0\n' + body2_acer = ';;0;0;Trade;0;4692.0\n' + + assert body1_lenovo in csv_str + assert body2_lenovo in csv_str + assert body2_acer in csv_str + + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_metrics_action_status_for_containers(user: UserClient, user2: UserClient): + """ Checks one standard query of metrics for a container.""" + # Insert computer + lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') + snap, _ = user.post(json_encode(lenovo), res=ma.Snapshot) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + devices = [('id', snap['device']['id'])] + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices) + request_post = { + 'type': 'Trade', + 'devices': [snap['device']['id']], + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=ma.Action, data=request_post) + + request_post = { + 'filename': 'test.pdf', + 'hash': 'bbbbbbbb', + 'url': 'http://www.ereuse.org/', + 'weight': 150, + 'lot': lot['id'] + } + tradedocument, _ = user.post(res=TradeDocument, data=request_post) + action = {'type': ma.Recycling.t, 'devices': [], 'documents': [tradedocument['id']]} + action, _ = user.post(action, res=ma.Action) + trade = TradeDocument.query.one() + + assert str(trade.actions[-1].id) == action['id'] + + csv_str, _ = user.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + + body1 = ';bbbbbbbb;test.pdf;Trade-Container;foo@foo.com;foo2@foo.com;Supplier;False;;;;;150.0;' + body2 = ';;0;0;Trade-Container;0;0' + assert len(csv_str.split('\n')) == 4 + assert body1 in csv_str.split('\n')[-2] + assert body2 in csv_str.split('\n')[-2] + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_visual_metrics_for_old_owners(user: UserClient, user2: UserClient): + """ Checks if one old owner can see the metrics in a trade enviroment.""" + # Insert computer + lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') + snap1, _ = user.post(json_encode(lenovo), res=ma.Snapshot) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + devices = [('id', snap1['device']['id'])] + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices) + request_post = { + 'type': 'Trade', + 'devices': [snap1['device']['id']], + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + trade, _ = user.post(res=ma.Action, data=request_post) + + request_confirm = { + 'type': 'Confirm', + 'action': trade['id'], + 'devices': [snap1['device']['id']] + } + user2.post(res=ma.Action, data=request_confirm) + + action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} + action_use, _ = user.post(action, res=ma.Action) + csv_supplier, _ = user.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + csv_receiver, _ = user2.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + body = ';;0;0;Trade;0;0\n' + + assert body in csv_receiver + assert body in csv_supplier + assert csv_receiver == csv_supplier + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_bug_trade_confirmed(user: UserClient, user2: UserClient): + """When the receiber do a Trade, then the confirmation is wrong.""" + lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') + snap1, _ = user.post(json_encode(lenovo), res=ma.Snapshot) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + devices = [('id', snap1['device']['id'])] + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=devices) + request_post = { + 'type': 'Trade', + 'devices': [snap1['device']['id']], + 'userFromEmail': user2.email, + 'userToEmail': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + trade, _ = user.post(res=ma.Action, data=request_post) + + csv_not_confirmed, _ = user.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + request_confirm = { + 'type': 'Confirm', + 'action': trade['id'], + 'devices': [snap1['device']['id']] + } + user2.post(res=ma.Action, data=request_confirm) + csv_confirmed, _ = user2.get(res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']})]) + + body_not_confirmed = "Trade;foo2@foo.com;foo@foo.com;Receiver;NeedConfirmation;" + body_confirmed = "Trade;foo2@foo.com;foo@foo.com;Receiver;TradeConfirmed;" + + assert body_not_confirmed in csv_not_confirmed + assert body_confirmed in csv_confirmed diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py index eaeca85c..130413c8 100644 --- a/tests/test_snapshot.py +++ b/tests/test_snapshot.py @@ -37,7 +37,6 @@ from tests import conftest @pytest.mark.mvp @pytest.mark.usefixtures('auth_app_context') -# cayop def test_snapshot_model(): """Tests creating a Snapshot with its relationships ensuring correct DB mapping. @@ -318,7 +317,7 @@ def test_snapshot_tag_inner_tag(user: UserClient, tag_id: str, app: Devicehub): snapshot_and_check(user, b, action_types=(RateComputer.t, BenchmarkProcessor.t, VisualTest.t)) with app.app_context(): - tag = Tag.query.one() # type: Tag + tag = Tag.query.all()[0] # type: Tag assert tag.device_id == 3, 'Tag should be linked to the first device' @@ -358,7 +357,7 @@ def test_snapshot_different_properties_same_tags(user: UserClient, tag_id: str): def test_snapshot_upload_twice_uuid_error(user: UserClient): pc1 = file('basic.snapshot') user.post(pc1, res=Snapshot) - user.post(pc1, res=Snapshot, status=UniqueViolation) + user.post(pc1, res=Snapshot, status=400) @pytest.mark.mvp diff --git a/tests/test_tag.py b/tests/test_tag.py index 67d59ccc..f852a206 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -2,6 +2,7 @@ import pathlib import pytest import requests_mock +from flask import g from boltons.urlutils import URL from ereuse_utils.session import DevicehubClient from pytest import raises @@ -11,6 +12,7 @@ from teal.marshmallow import ValidationError from ereuse_devicehub.client import UserClient, Client from ereuse_devicehub.db import db from ereuse_devicehub.devicehub import Devicehub +from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.action.models import Snapshot from ereuse_devicehub.resources.agent.models import Organization from ereuse_devicehub.resources.device.models import Desktop, Device @@ -19,7 +21,7 @@ from ereuse_devicehub.resources.tag import Tag from ereuse_devicehub.resources.tag.view import CannotCreateETag, LinkedToAnotherDevice, \ TagNotLinked from tests import conftest -from tests.conftest import file, yaml2json, json_encode +from tests.conftest import yaml2json, json_encode @pytest.mark.mvp @@ -41,6 +43,7 @@ def test_create_tag(user: UserClient): @pytest.mark.usefixtures(conftest.app_context.__name__) def test_create_tag_with_device(user: UserClient): """Creates a tag specifying linked with one device.""" + g.user = User.query.one() pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) db.session.add(pc) db.session.commit() @@ -60,13 +63,14 @@ def test_create_tag_with_device(user: UserClient): def test_delete_tags(user: UserClient, client: Client): """Delete a named tag.""" # Delete Tag Named + g.user = User.query.one() pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) db.session.add(pc) db.session.commit() tag = Tag(id='bar', owner_id=user.user['id'], device_id=pc.id) db.session.add(tag) db.session.commit() - tag = Tag.query.one() + tag = Tag.query.all()[-1] assert tag.id == 'bar' # Is not possible delete one tag linked to one device res, _ = user.delete(res=Tag, item=tag.id, status=422) @@ -88,12 +92,12 @@ def test_delete_tags(user: UserClient, client: Client): tag = Tag(id='bar-1', org=org, provider=URL('http://foo.bar'), owner_id=user.user['id']) db.session.add(tag) db.session.commit() - tag = Tag.query.one() + tag = Tag.query.all()[-1] assert tag.id == 'bar-1' res, _ = user.delete(res=Tag, item=tag.id, status=422) msg = 'This tag {} is unnamed tag. It is imposible delete.'.format(tag.id) assert msg in res['message'] - tag = Tag.query.one() + tag = Tag.query.all()[-1] assert tag.id == 'bar-1' @@ -182,6 +186,7 @@ def test_tag_get_device_from_tag_endpoint(app: Devicehub, user: UserClient): """Checks getting a linked device from a tag endpoint""" with app.app_context(): # Create a pc with a tag + g.user = User.query.one() tag = Tag(id='foo-bar', owner_id=user.user['id']) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc.tags.add(tag) @@ -213,6 +218,7 @@ def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: Us system should not return any of both (to be deterministic) so it should raise an exception. """ + g.user = User.query.all()[0] db.session.add(Tag(id='foo', secondary='bar', owner_id=user.user['id'])) db.session.commit() @@ -276,6 +282,7 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient): Checks search has the term. """ with app.app_context(): + g.user = User.query.one() db.session.add(Tag('foo-bar', secondary='foo-sec', owner_id=user.user['id'])) desktop = Desktop(serial_number='foo', chassis=ComputerChassis.AllInOne, owner_id=user.user['id']) db.session.add(desktop) @@ -284,7 +291,7 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient): devicehub_id = desktop.devicehub_id user.put({}, res=Tag, item='foo-bar/device/{}'.format(desktop_id), status=204) device, _ = user.get(res=Device, item=devicehub_id) - assert device['tags'][0]['id'] == 'foo-bar' + assert 'foo-bar' in [x['id'] for x in device['tags']] # Device already linked # Just returns an OK to conform to PUT as anything changes @@ -323,8 +330,8 @@ def test_tag_secondary_workbench_link_find(user: UserClient): s['device']['tags'] = [{'id': 'foo', 'secondary': 'bar', 'type': 'Tag'}] snapshot, _ = user.post(json_encode(s), res=Snapshot) device, _ = user.get(res=Device, item=snapshot['device']['devicehubID']) - assert device['tags'][0]['id'] == 'foo' - assert device['tags'][0]['secondary'] == 'bar' + assert 'foo' in [x['id'] for x in device['tags']] + assert 'bar' in [x.get('secondary') for x in device['tags']] r, _ = user.get(res=Device, query=[('search', 'foo'), ('filter', {'type': ['Computer']})]) assert len(r['items']) == 1 @@ -412,6 +419,7 @@ def test_get_tag_permissions(app: Devicehub, user: UserClient, user2: UserClient """Creates a tag specifying a custom organization.""" with app.app_context(): # Create a pc with a tag + g.user = User.query.all()[0] tag = Tag(id='foo-bar', owner_id=user.user['id']) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc.tags.add(tag) @@ -424,5 +432,5 @@ def test_get_tag_permissions(app: Devicehub, user: UserClient, user2: UserClient computer2, res2 = user2.get(url, None) assert res.status_code == 200 assert res2.status_code == 200 - assert len(computer['items']) == 1 + assert len(computer['items']) == 2 assert len(computer2['items']) == 0 diff --git a/tests/test_workbench.py b/tests/test_workbench.py index 8dd2e65f..2eb161a1 100644 --- a/tests/test_workbench.py +++ b/tests/test_workbench.py @@ -66,8 +66,8 @@ def test_workbench_server_condensed(user: UserClient): assert device['rate']['rating'] == 1 assert device['rate']['type'] == RateComputer.t # TODO JN why haven't same order in actions on each execution? - assert device['actions'][2]['type'] == BenchmarkProcessor.t or device['actions'][2]['type'] == BenchmarkRamSysbench.t - assert device['tags'][0]['id'] == 'tag1' + assert any([ac['type'] in [BenchmarkProcessor.t, BenchmarkRamSysbench.t] for ac in device['actions']]) + assert 'tag1' in [x['id'] for x in device['tags']] @pytest.mark.xfail(reason='Functionality not yet developed.') @@ -184,7 +184,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): assert pc['serialNumber'] == 'b8oaas048286' assert pc['manufacturer'] == 'asustek computer inc.' assert pc['hid'] == 'laptop-asustek_computer_inc-1001pxd-b8oaas048286-14:da:e9:42:f6:7c' - assert pc['tags'] == [] + assert len(pc['tags']) == 1 assert pc['networkSpeeds'] == [100, 0], 'Although it has WiFi we do not know the speed' assert pc['rate'] rate = pc['rate']