Merge branch 'testing' into feature/delete-devices

This commit is contained in:
Cayo Puigdefabregas 2021-11-19 12:07:14 +01:00
commit 3b41ddb66a
29 changed files with 1194 additions and 493 deletions

View File

@ -14,36 +14,38 @@ ml).
## [1.0.10-beta] ## [1.0.10-beta]
- [addend] #170 can delete/deactivate devices. - [addend] #170 can delete/deactivate devices.
- [bugfix] #168 can to do a trade without 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] ## [1.0.9-beta]
- [addend] #159 external document as proof of erase of disk - [added] #159 external document as proof of erase of disk
- [addend] #162 adding lot for devices unassigned - [added] #162 adding lot for devices unassigned
## [1.0.8-beta] ## [1.0.8-beta]
- [bugfix] #161 fixing DataStorage with bigInteger - [bugfix] #161 fixing DataStorage with bigInteger
## [1.0.7-beta] ## [1.0.7-beta]
- [addend] #158 support for encrypted snapshots data - [added] #158 support for encrypted snapshots data
- [addend] #135 adding trade system - [added] #135 adding trade system
- [addend] #140 adding endpoint for download the settings for usb workbench - [added] #140 adding endpoint for download the settings for usb workbench
## [1.0.6-beta] ## [1.0.6-beta]
- [bugfix] #143 biginteger instead of integer in TestDataStorage - [bugfix] #143 biginteger instead of integer in TestDataStorage
## [1.0.5-beta] ## [1.0.5-beta]
- [addend] #124 adding endpoint for extract the internal stats of use - [added] #124 adding endpoint for extract the internal stats of use
- [addend] #122 system for verify all documents that it's produced from devicehub - [added] #122 system for verify all documents that it's produced from devicehub
- [addend] #127 add one code for every named tag - [added] #127 add one code for every named tag
- [addend] #131 add one code for every device - [added] #131 add one code for every device
- [bugfix] #138 search device with devicehubId - [bugfix] #138 search device with devicehubId
## [1.0.4-beta] ## [1.0.4-beta]
- [addend] #95 adding endpoint for check the hash of one report - [added] #95 adding endpoint for check the hash of one report
- [addend] #98 adding endpoint for insert a new live - [added] #98 adding endpoint for insert a new live
- [addend] #98 adding endpoint for get all licences in one query - [added] #98 adding endpoint for get all licences in one query
- [addend] #102 adding endpoint for download metrics - [added] #102 adding endpoint for download metrics
- [bugfix] #100 fixing bug of scheme live - [bugfix] #100 fixing bug of scheme live
- [bugfix] #101 fixing bug when 2 users have one device and launch one live - [bugfix] #101 fixing bug when 2 users have one device and launch one live
- [changes] #114 clean blockchain of all models - [changes] #114 clean blockchain of all models
@ -52,11 +54,11 @@ ml).
- [remove] #114 remove proof system - [remove] #114 remove proof system
## [1.0.3-beta] ## [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 - [changed] #94 change form of snapshot manual
## [1.0.2-beta] ## [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 - [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 - [fixed] #91 The most old time allow is 1970-01-01

View File

@ -139,7 +139,7 @@ class Dummy:
res=Lot, res=Lot,
item='{}/devices'.format(lot_user['id']), item='{}/devices'.format(lot_user['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 1, 4)]) query=[('id', pc) for pc in itertools.islice(pcs, 1, 4)])
assert len(lot['devices']) # assert len(lot['devices'])
lot2, _ = user2.post({}, lot2, _ = user2.post({},
res=Lot, res=Lot,

View File

@ -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()}')

View File

@ -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

View File

@ -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()}')

View File

@ -285,16 +285,16 @@ class ConfirmDef(ActionDef):
SCHEMA = schemas.Confirm SCHEMA = schemas.Confirm
class ConfirmRevokeDef(ActionDef):
VIEW = None
SCHEMA = schemas.ConfirmRevoke
class RevokeDef(ActionDef): class RevokeDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.Revoke SCHEMA = schemas.Revoke
class ConfirmRevokeDef(ActionDef):
VIEW = None
SCHEMA = schemas.ConfirmRevoke
class TradeDef(ActionDef): class TradeDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.Trade SCHEMA = schemas.Trade

View File

@ -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.models import STR_SM_SIZE, Thing
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
from ereuse_devicehub.resources.tradedocument.models import TradeDocument from ereuse_devicehub.resources.tradedocument.models import TradeDocument
from ereuse_devicehub.resources.device.metrics import TradeMetrics
class JoinedTableMixin: class JoinedTableMixin:
@ -303,6 +304,31 @@ class ActionDevice(db.Model):
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDevices.id), action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDevices.id),
primary_key=True) 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): class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices):
@ -1359,6 +1385,16 @@ class ActionStatus(JoinedTableMixin, ActionWithMultipleTradeDocuments):
default=lambda: g.user.id) default=lambda: g.user.id)
rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id) rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id)
rol_user_comment = """The user that .""" 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): class Recycling(ActionStatus):
@ -1582,11 +1618,11 @@ class Revoke(Confirm):
"""Users can revoke one confirmation of one action trade""" """Users can revoke one confirmation of one action trade"""
class ConfirmRevoke(Confirm): # class ConfirmRevoke(Confirm):
"""Users can confirm and accept one action revoke""" # """Users can confirm and accept one action revoke"""
def __repr__(self) -> str: # def __repr__(self) -> str:
return '<{0.t} {0.id} accepted by {0.user}>'.format(self) # return '<{0.t} {0.id} accepted by {0.user}>'.format(self)
class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments): class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments):
@ -1632,6 +1668,16 @@ class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments):
cascade=CASCADE_OWN), cascade=CASCADE_OWN),
primaryjoin='Trade.lot_id == Lot.id') 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: def __repr__(self) -> str:
return '<{0.t} {0.id} executed by {0.author}>'.format(self) return '<{0.t} {0.id} executed by {0.author}>'.format(self)

View File

@ -439,17 +439,21 @@ class ActionStatus(Action):
@pre_load @pre_load
def put_devices(self, data: dict): def put_devices(self, data: dict):
if not 'devices' in data.keys(): if 'devices' not in data.keys():
data['devices'] = [] data['devices'] = []
@post_load @post_load
def put_rol_user(self, data: dict): def put_rol_user(self, data: dict):
for dev in data['devices']: 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 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['rol_user'] = trade.user_to
data['trade'] = trade
class Recycling(ActionStatus): class Recycling(ActionStatus):
@ -581,6 +585,10 @@ class Revoke(ActionWithMultipleDevices):
raise ValidationError(txt) raise ValidationError(txt)
class ConfirmRevoke(Revoke):
pass
class ConfirmDocument(ActionWithMultipleDocuments): class ConfirmDocument(ActionWithMultipleDocuments):
__doc__ = m.Confirm.__doc__ __doc__ = m.Confirm.__doc__
action = NestedOn('Action', only_query='id') action = NestedOn('Action', only_query='id')
@ -635,7 +643,7 @@ class RevokeDocument(ActionWithMultipleDocuments):
class ConfirmRevokeDocument(ActionWithMultipleDocuments): class ConfirmRevokeDocument(ActionWithMultipleDocuments):
__doc__ = m.ConfirmRevoke.__doc__ __doc__ = m.ConfirmRevokeDocument.__doc__
action = NestedOn('Action', only_query='id') action = NestedOn('Action', only_query='id')
@validates_schema @validates_schema
@ -662,66 +670,6 @@ class ConfirmRevokeDocument(ActionWithMultipleDocuments):
data['action'] = doc.actions[-1] 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): class Trade(ActionWithMultipleDevices):
__doc__ = m.Trade.__doc__ __doc__ = m.Trade.__doc__
date = DateTime(data_key='date', required=False) date = DateTime(data_key='date', required=False)

View File

@ -3,7 +3,7 @@ from sqlalchemy.util import OrderedSet
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from ereuse_devicehub.db import db 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, Revoke, RevokeDocument, ConfirmDocument,
ConfirmRevokeDocument) ConfirmRevokeDocument)
from ereuse_devicehub.resources.user.models import User 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 # check than the user than want to do the action is one of the users
# involved in the action # 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" txt = "You do not participate in this trading"
raise ValidationError(txt) raise ValidationError(txt)
@ -181,17 +181,17 @@ class ConfirmView(ConfirmMixin):
then remove the list this device of the list of devices of this action then remove the list this device of the list of devices of this action
""" """
real_devices = [] real_devices = []
trade = data['action']
lot = trade.lot
for dev in data['devices']: for dev in data['devices']:
ac = dev.last_action_trading if dev.trading(lot, simple=True) not in ['NeedConfirmation', 'NeedConfirmRevoke']:
if ac.type == Confirm.t and not ac.user == g.user: raise ValidationError('Some devices not possible confirm.')
real_devices.append(dev)
data['devices'] = OrderedSet(real_devices)
# Change the owner for every devices # Change the owner for every devices
for dev in data['devices']: for dev in data['devices']:
user_to = data['action'].user_to if dev.trading(lot) == 'NeedConfirmation':
dev.change_owner(user_to) user_to = data['action'].user_to
dev.change_owner(user_to)
class RevokeView(ConfirmMixin): class RevokeView(ConfirmMixin):
@ -215,57 +215,12 @@ class RevokeView(ConfirmMixin):
def validate(self, data): def validate(self, data):
"""All devices need to have the status of DoubleConfirmation.""" """All devices need to have the status of DoubleConfirmation."""
### check ### devices = data['devices']
if not data['devices']: if not devices:
raise ValidationError('Devices not exist.') 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 lot = data['action'].lot
self.model = delete_from_trade(lot, ids) self.model = delete_from_trade(lot, devices)
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)
class ConfirmDocumentMixin(): class ConfirmDocumentMixin():

View File

@ -15,7 +15,7 @@ from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.action.models import (Action, Snapshot, VisualTest, from ereuse_devicehub.resources.action.models import (Action, Snapshot, VisualTest,
InitTransfer, Live, Allocate, Deallocate, 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 import trade as trade_view
from ereuse_devicehub.resources.action.views.snapshot import SnapshotView, save_json, move_json from ereuse_devicehub.resources.action.views.snapshot import SnapshotView, save_json, move_json
from ereuse_devicehub.resources.action.views.documents import ErasedView 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) revoke = trade_view.RevokeView(json, resource_def, self.schema)
return revoke.post() return revoke.post()
if json['type'] == ConfirmRevoke.t: if json['type'] == 'ConfirmRevoke':
confirm_revoke = trade_view.ConfirmRevokeView(json, resource_def, self.schema) revoke = trade_view.RevokeView(json, resource_def, self.schema)
return confirm_revoke.post() return revoke.post()
if json['type'] == 'RevokeDocument': if json['type'] == 'RevokeDocument':
revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema) revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema)

View File

@ -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

View File

@ -1,17 +1,17 @@
import pathlib import pathlib
import copy import copy
import time
from flask import g from flask import g
from contextlib import suppress from contextlib import suppress
from fractions import Fraction from fractions import Fraction
from itertools import chain from itertools import chain
from operator import attrgetter from operator import attrgetter
from typing import Dict, List, Set from typing import Dict, List, Set
from flask_sqlalchemy import event
from boltons import urlutils from boltons import urlutils
from citext import CIText from citext import CIText
from flask_sqlalchemy import event
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
from flask import g
from more_itertools import unique_everseen from more_itertools import unique_everseen
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \ from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
Sequence, SmallInteger, Unicode, inspect, text 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 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.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.user.models import User
from ereuse_devicehub.resources.device.metrics import Metrics
def create_code(context): def create_code(context):
_id = Device.query.order_by(Device.id.desc()).first() or 1 _id = Device.query.order_by(Device.id.desc()).first() or 3
if not _id == 1: if not _id == 3:
_id = _id.id + 1 _id = _id.id + 1
return hashcode.encode(_id) return hashcode.encode(_id)
@ -173,7 +174,16 @@ class Device(Thing):
Actions are returned by descending ``created`` time. 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 @property
def problems(self): def problems(self):
@ -232,7 +242,7 @@ class Device(Thing):
:return a list of actions: :return a list of actions:
""" """
hide_actions = ['Price', 'EreusePrice'] 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() actions.reverse()
return actions return actions
@ -289,7 +299,7 @@ class Device(Thing):
status_actions = [ac.t for ac in states.Status.actions()] status_actions = [ac.t for ac in states.Status.actions()]
history = [] history = []
for ac in self.actions: for ac in self.actions:
if not ac.t in status_actions: if ac.t not in status_actions:
continue continue
if not history: if not history:
history.append(ac) history.append(ac)
@ -304,75 +314,90 @@ class Device(Thing):
return history return history
@property @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 """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.
# trade = 'Trade' If you need to do one simple and generic response you can put simple=True for that."""
confirm = 'Confirm' if not hasattr(lot, 'trade'):
need_confirm = 'NeedConfirmation'
double_confirm = 'TradeConfirmed'
revoke = 'Revoke'
revoke_pending = 'RevokePending'
confirm_revoke = 'ConfirmRevoke'
# revoke_confirmed = 'RevokeConfirmed'
# return the correct status of trade depending of the user
##### 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
ac = self.last_action_trading
if not ac:
return return
first_owner = self.which_user_put_this_device_in_trace() Status = {0: 'Trade',
1: 'Confirm',
2: 'NeedConfirmation',
3: 'TradeConfirmed',
4: 'Revoke',
5: 'NeedConfirmRevoke',
6: 'RevokeConfirmed'}
if ac.type == confirm_revoke: trade = lot.trade
# can to do revoke_confirmed user_from = trade.user_from
return confirm_revoke user_to = trade.user_to
status = 0
last_user = None
if ac.type == revoke: if not hasattr(trade, 'acceptances'):
if ac.user == g.user: return Status[status]
# can todo revoke_pending
return revoke_pending
else:
# can to do confirm_revoke
return revoke
if ac.type == confirm: for ac in self.actions:
if not first_owner: if ac.t not in ['Confirm', 'Revoke']:
return continue
if ac.user == first_owner: if ac.user not in [user_from, user_to]:
if first_owner == g.user: continue
# can to do revoke
return confirm if ac.t == 'Confirm' and ac.action == trade:
else: if status in [0, 6]:
# can to do confirm if simple:
return need_confirm status = 2
else: continue
# can to do revoke status = 1
return double_confirm 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 status in [1, 2]:
if last_user != ac.user:
status = 3
last_user = ac.user
continue
if status in [4, 5]:
status = 3
last_user = ac.user
continue
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 @property
def revoke(self): def revoke(self):
@ -428,8 +453,8 @@ class Device(Thing):
# TODO @cayop uncomment this lines for link the possessor with the device # TODO @cayop uncomment this lines for link the possessor with the device
# from ereuse_devicehub.resources.action.models import Receive # from ereuse_devicehub.resources.action.models import Receive
# with suppress(LookupError): # with suppress(LookupError):
# action = self.last_action_of(Receive) # action = self.last_action_of(Receive)
# return action.agent_to # return action.agent_to
@property @property
def working(self): def working(self):
@ -478,15 +503,15 @@ class Device(Thing):
def which_user_put_this_device_in_trace(self): def which_user_put_this_device_in_trace(self):
"""which is the user than put this device in this trade""" """which is the user than put this device in this trade"""
actions = copy.copy(self.actions) actions = copy.copy(self.actions)
actions.sort(key=lambda x: x.created)
actions.reverse() actions.reverse()
last_ac = None
# search the automatic Confirm # search the automatic Confirm
for ac in actions: for ac in actions:
if ac.type == 'Trade': if ac.type == 'Trade':
return last_ac.user action_device = [x for x in ac.actions_device if x.device == self][0]
if ac.type == 'Confirm': if action_device.author:
last_ac = ac return action_device.author
return ac.author
def change_owner(self, new_user): def change_owner(self, new_user):
"""util for change the owner one device""" """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 This method get a list of values for calculate a metrics from a spreadsheet
""" """
actions = copy.copy(self.actions) metrics = Metrics(device=self)
actions.sort(key=lambda x: x.created) return metrics.get_metrics()
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
def __lt__(self, other): def __lt__(self, other):
return self.id < other.id return self.id < other.id
@ -681,7 +662,13 @@ class Computer(Device):
@property @property
def actions(self) -> list: 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 @property
def ram_size(self) -> int: def ram_size(self) -> int:
@ -1212,3 +1199,15 @@ class Manufacturer(db.Model):
listener_reset_field_updated_in_actual_time(Device) 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)

View File

@ -1,7 +1,7 @@
import datetime import datetime
from marshmallow import post_load, pre_load, fields as f 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 marshmallow.validate import Length, OneOf, Range
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from stdnum import imei, meid 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) 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) 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) 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__) 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) 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__) problems = NestedOn('Action', many=True, dump_only=True, description=m.Device.problems.__doc__)
url = URL(dump_only=True, description=m.Device.url.__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', lots = NestedOn('Lot',
many=True, many=True,
dump_only=True, dump_only=True,
description='The lots where this device is directly under.') description='The lots where this device is directly under.')
rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__) rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__)
price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__) price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__)
# trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__) tradings = Dict(dump_only=True, description='')
trading = SanitizedStr(dump_only=True, description='')
physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__) 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__) 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') physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
production_date = DateTime('iso', production_date = DateTime('iso',
description=m.Device.updated.comment, description=m.Device.updated.comment,
@ -99,6 +101,7 @@ class Device(Thing):
class Computer(Device): class Computer(Device):
__doc__ = m.Computer.__doc__ __doc__ = m.Computer.__doc__
# TODO TimeOut 1. Comment components if there are time out.
components = NestedOn('Component', components = NestedOn('Component',
many=True, many=True,
dump_only=True, dump_only=True,
@ -129,7 +132,7 @@ class Computer(Device):
description=m.Computer.privacy.__doc__) description=m.Computer.privacy.__doc__)
amount = Integer(validate=f.validate.Range(min=0, max=100), amount = Integer(validate=f.validate.Range(min=0, max=100),
description=m.Computer.amount.__doc__) 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') owner_id = UUID(data_key='ownerID')
transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment) transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment)
receiver_id = UUID(data_key='receiverID') receiver_id = UUID(data_key='receiverID')

View File

@ -37,7 +37,6 @@ class Trading(State):
Trade = e.Trade Trade = e.Trade
Confirm = e.Confirm Confirm = e.Confirm
Revoke = e.Revoke Revoke = e.Revoke
ConfirmRevoke = e.ConfirmRevoke
Cancelled = e.CancelTrade Cancelled = e.CancelTrade
Sold = e.Sell Sold = e.Sell
Donated = e.Donate Donated = e.Donate

View File

@ -414,6 +414,8 @@ def none2str(string):
return '' return ''
return format(string) return format(string)
def get_action(component, action): def get_action(component, action):
""" Filter one action from a component or return None """ """ Filter one action from a component or return None """
result = [a for a in component.actions if a.type == action] 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 # General information about allocates, deallocate and lives
self['DHID'] = allocate['devicehubID'] self['DHID'] = allocate['devicehubID']
self['Hid'] = allocate['hid'] self['Hid'] = allocate['hid']
self['Start'] = allocate['start'] self['Document-Name'] = allocate['document_name']
self['FinalUserCode'] = allocate['finalUserCode'] self['Action-Type'] = allocate['action_type']
self['NumEndUsers'] = allocate['numEndUsers'] 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['UsageTimeAllocate'] = allocate['usageTimeAllocate']
self['Type'] = allocate['type'] self['Type'] = allocate['type']
self['LiveCreate'] = allocate['liveCreate'] self['LiveCreate'] = allocate['liveCreate']

View File

@ -3,11 +3,9 @@ import enum
import uuid import uuid
import time import time
import datetime import datetime
import pathlib
from collections import OrderedDict from collections import OrderedDict
from io import StringIO from io import StringIO
from typing import Callable, Iterable, Tuple from typing import Callable, Iterable, Tuple
from decouple import config
import boltons import boltons
import flask import flask
@ -32,6 +30,8 @@ from ereuse_devicehub.resources.documents.device_row import (DeviceRow, StockRow
InternalStatsRow) InternalStatsRow)
from ereuse_devicehub.resources.lot import LotView from ereuse_devicehub.resources.lot import LotView
from ereuse_devicehub.resources.lot.models import Lot 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 from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash, verify_hash
@ -90,7 +90,6 @@ class DocumentView(DeviceView):
res = flask.make_response(template) res = flask.make_response(template)
return res return res
@staticmethod @staticmethod
def erasure(query: db.Query): def erasure(query: db.Query):
def erasures(): def erasures():
@ -151,7 +150,7 @@ class DevicesDocumentView(DeviceView):
class ActionsDocumentView(DeviceView): class ActionsDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1)) @cache(datetime.timedelta(minutes=1))
def find(self, args: dict): 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) return self.generate_post_csv(query)
def generate_post_csv(self, query): def generate_post_csv(self, query):
@ -159,13 +158,26 @@ class ActionsDocumentView(DeviceView):
data = StringIO() data = StringIO()
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"') cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
first = True first = True
devs_id = []
for device in query: for device in query:
devs_id.append(device.id)
for allocate in device.get_metrics(): for allocate in device.get_metrics():
d = ActionRow(allocate) d = ActionRow(allocate)
if first: if first:
cw.writerow(d.keys()) cw.writerow(d.keys())
first = False first = False
cw.writerow(d.values()) 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') bfile = data.getvalue().encode('utf-8')
output = make_response(bfile) output = make_response(bfile)
insert_hash(bfile) insert_hash(bfile)
@ -185,11 +197,11 @@ class LotsDocumentView(LotView):
cw = csv.writer(data) cw = csv.writer(data)
first = True first = True
for lot in query: for lot in query:
l = LotRow(lot) _lot = LotRow(lot)
if first: if first:
cw.writerow(l.keys()) cw.writerow(_lot.keys())
first = False first = False
cw.writerow(l.values()) cw.writerow(_lot.values())
bfile = data.getvalue().encode('utf-8') bfile = data.getvalue().encode('utf-8')
output = make_response(bfile) output = make_response(bfile)
insert_hash(bfile) insert_hash(bfile)
@ -275,7 +287,7 @@ class StampsView(View):
ok = '100% coincidence. The attached file contains data 100% existing in \ ok = '100% coincidence. The attached file contains data 100% existing in \
to our backend' to our backend'
result = ('Bad', bad) 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', 'image/jpeg', 'image/png', 'text/html',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet',
@ -304,9 +316,9 @@ class InternalStatsView(DeviceView):
create = '{}-{}'.format(ac.created.year, ac.created.month) create = '{}-{}'.format(ac.created.year, ac.created.month)
user = ac.author.email user = ac.author.email
if not user in d: if user not in d:
d[user] = {} d[user] = {}
if not create in d[user]: if create not in d[user]:
d[user][create] = [] d[user][create] = []
d[user][create].append(ac) d[user][create].append(ac)
@ -434,4 +446,3 @@ class DocumentDef(Resource):
auth=app.auth) auth=app.auth)
wbconf_view = app.auth.requires_auth(wbconf_view) wbconf_view = app.auth.requires_auth(wbconf_view)
self.add_url_rule('/wbconf/<string:wbtype>', view_func=wbconf_view, methods=get) self.add_url_rule('/wbconf/<string:wbtype>', view_func=wbconf_view, methods=get)

View File

@ -5,14 +5,32 @@ from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.deliverynote import schemas as s_deliverynote from ereuse_devicehub.resources.deliverynote import schemas as s_deliverynote
from ereuse_devicehub.resources.device import schemas as s_device from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.action import schemas as s_action 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.enums import TransferState
from ereuse_devicehub.resources.lot import models as m from ereuse_devicehub.resources.lot import models as m
from ereuse_devicehub.resources.models import STR_SIZE from ereuse_devicehub.resources.models import STR_SIZE
from ereuse_devicehub.resources.schemas import Thing 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) id = f.UUID(dump_only=True)
name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True) name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
description = SanitizedStr(description=m.Lot.description.comment) description = SanitizedStr(description=m.Lot.description.comment)
@ -29,4 +47,11 @@ class Lot(Thing):
receiver_address = SanitizedStr(validate=f.validate.Length(max=42)) receiver_address = SanitizedStr(validate=f.validate.Length(max=42))
deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True) deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True)
documents = NestedOn('TradeDocument', many=True, 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)

View File

@ -1,4 +1,5 @@
import uuid import uuid
from sqlalchemy.util import OrderedSet
from collections import deque from collections import deque
from enum import Enum from enum import Enum
from typing import Dict, List, Set, Union 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.db import db
from ereuse_devicehub.query import things_response from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.device.models import Device, Computer 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 from ereuse_devicehub.resources.lot.models import Lot, Path
@ -230,7 +231,7 @@ class LotDeviceView(LotBaseChildrenView):
return return
devices = set(Device.query.filter(Device.id.in_(ids)).filter( devices = set(Device.query.filter(Device.id.in_(ids)).filter(
Device.owner==g.user)) Device.owner == g.user))
lot.devices.update(devices) lot.devices.update(devices)
@ -246,7 +247,8 @@ class LotDeviceView(LotBaseChildrenView):
return return
if lot.trade: 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: if not g.user == lot.owner:
txt = 'This is not your lot' txt = 'This is not your lot'
@ -258,49 +260,45 @@ class LotDeviceView(LotBaseChildrenView):
lot.devices.difference_update(devices) lot.devices.difference_update(devices)
def delete_from_trade(lot: Lot, ids: Set[int]): def delete_from_trade(lot: Lot, devices: List):
users = [lot.trade.user_from.id, lot.trade.user_to.id] users = [lot.trade.user_from, lot.trade.user_to]
if not g.user.id in users: if g.user not in users:
# theoretically this case is impossible # theoretically this case is impossible
txt = 'This is not your trade' txt = 'This is not your trade'
raise ma.ValidationError(txt) raise ma.ValidationError(txt)
devices = set(Device.query.filter(Device.id.in_(ids)).filter( # we need lock the action revoke for devices than travel for futures trades
Device.owner_id.in_(users))) 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 drop_of_lot = []
without_confirms = set() # set of devs without confirms of user2 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 if not lot.trade.confirm:
# have only one confirmation and is from the same user than try to do drop_of_lot.append(dev)
# now the revoke action without_confirms.append(dev)
if lot.trade.confirm: dev.reset_owner()
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()
# we need to mark one revoke for every devs revoke = Revoke(action=lot.trade, user=g.user, devices=set(devices))
revoke = Revoke(action=lot.trade, user=g.user, devices=devices)
db.session.add(revoke) db.session.add(revoke)
if not lot.trade.confirm:
# if the trade is with phantom account
without_confirms = devices
if without_confirms: if without_confirms:
confirm_revoke = ConfirmRevoke( phantom = lot.trade.user_to
action=revoke, if lot.trade.user_to == g.user:
user=g.user, phantom = lot.trade.user_from
devices=without_confirms
phantom_revoke = Revoke(
action=lot.trade,
user=phantom,
devices=set(without_confirms)
) )
db.session.add(confirm_revoke) db.session.add(phantom_revoke)
lot.devices.difference_update(without_confirms)
lot.trade.devices = lot.devices
lot.devices.difference_update(OrderedSet(drop_of_lot))
return revoke return revoke

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -328,7 +328,7 @@ def test_outgoinlot_status_actions(action_model: models.Action, user: UserClient
assert device['actions'][-1]['id'] == action['id'] assert device['actions'][-1]['id'] == action['id']
assert action['author']['id'] == user.user['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 @pytest.mark.mvp
@ -1396,6 +1396,7 @@ def test_confirm_revoke(user: UserClient, user2: UserClient):
user.post(res=models.Action, data=request_post) user.post(res=models.Action, data=request_post)
trade = models.Trade.query.one() trade = models.Trade.query.one()
device = trade.devices[0]
request_confirm = { request_confirm = {
'type': 'Confirm', 'type': 'Confirm',
@ -1416,9 +1417,10 @@ def test_confirm_revoke(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user2.post(res=models.Action, data=request_revoke) user2.post(res=models.Action, data=request_revoke)
# You can not to do one confirmation next of one revoke # You can to do one confirmation next of one revoke
user2.post(res=models.Action, data=request_confirm, status=422) user2.post(res=models.Action, data=request_confirm)
assert len(trade.acceptances) == 3 assert len(trade.acceptances) == 4
assert device.trading(trade.lot) == "TradeConfirmed"
@pytest.mark.mvp @pytest.mark.mvp
@ -1516,9 +1518,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
'type': 'Confirm', 'type': 'Confirm',
'action': trade.id, 'action': trade.id,
'devices': [ 'devices': [
snap1['device']['id'],
snap2['device']['id'],
snap3['device']['id'],
snap4['device']['id'], snap4['device']['id'],
snap5['device']['id'], snap5['device']['id'],
snap6['device']['id'], snap6['device']['id'],
@ -1554,31 +1553,28 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
# the SCRAP confirms the revoke action # the SCRAP confirms the revoke action
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device_10.actions[-1].id, 'action': trade.id,
'devices': [ 'devices': [
snap10['device']['id'] snap10['device']['id']
] ]
} }
user2.post(res=models.Action, data=request_confirm_revoke) 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 device_10.actions[-2].t == 'Revoke'
# assert len(trade.lot.devices) == len(trade.devices) == 9 # assert len(trade.lot.devices) == len(trade.devices) == 9
# assert not device_10 in trade.devices # assert not device_10 in trade.devices
# check validation error # check validation error
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device_10.actions[-1].id, 'action': trade.id,
'devices': [ 'devices': [
snap9['device']['id'] snap9['device']['id']
] ]
} }
user2.post(res=models.Action, data=request_confirm_revoke, status=422)
# The manager add again device_10 # The manager add again device_10
# assert len(trade.devices) == 9 # assert len(trade.devices) == 9
lot, _ = user.post({}, 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[-1].user == trade.user_from
assert device_10.actions[-2].t == 'Confirm' assert device_10.actions[-2].t == 'Confirm'
assert device_10.actions[-2].user == trade.user_to 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 # assert len(device_10.actions) == 13
@ -1772,31 +1768,23 @@ def test_trade_case1(user: UserClient, user2: UserClient):
user.post(res=models.Action, data=request_post) user.post(res=models.Action, data=request_post)
trade = models.Trade.query.one() trade = models.Trade.query.one()
lot, _ = user.post({}, lot = trade.lot
res=Lot, device = trade.devices[0]
item='{}/devices'.format(lot['id']),
query=devices[-1:])
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' user.delete({},
assert device1.actions[-1].t == 'Confirm' res=Lot,
assert device1.actions[-1].user == trade.user_to item='{}/devices'.format(lot.id),
assert device2.actions[-2].t == 'Trade' query=devices[:-1], status=200)
assert device2.actions[-1].t == 'Confirm'
assert device2.actions[-1].user == trade.user_to
lot, _ = user.delete({}, assert device not in trade.lot.devices
res=Lot, assert device.trading(trade.lot) == 'RevokeConfirmed'
item='{}/devices'.format(lot['id']), assert device.actions[-2].t == 'Confirm'
query=devices, status=200) assert device.actions[-1].t == 'Revoke'
assert device.actions[-1].user == trade.user_to
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
@pytest.mark.mvp @pytest.mark.mvp
@ -1855,12 +1843,13 @@ def test_trade_case2(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user.post(res=models.Action, data=request_revoke) user.post(res=models.Action, data=request_revoke)
assert device1.actions[-2].t == 'Revoke' assert device1.actions[-2].t == 'Confirm'
assert device1.actions[-1].t == 'ConfirmRevoke' assert device1.actions[-1].t == 'Revoke'
assert device1.actions[-1].user == trade.user_to assert device1.actions[-1].user == trade.user_to
assert device2.actions[-2].t == 'Revoke' assert device2.actions[-2].t == 'Confirm'
assert device2.actions[-1].t == 'ConfirmRevoke' assert device2.actions[-1].t == 'Revoke'
assert device2.actions[-1].user == trade.user_to assert device2.actions[-1].user == trade.user_to
assert device1.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -1868,7 +1857,6 @@ def test_trade_case2(user: UserClient, user2: UserClient):
def test_trade_case3(user: UserClient, user2: UserClient): def test_trade_case3(user: UserClient, user2: UserClient):
# the pRp (manatest_usecase_confirmationger) creates a temporary lot # the pRp (manatest_usecase_confirmationger) creates a temporary lot
lot, _ = user.post({'name': 'MyLot'}, res=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) snap1, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
snap2, _ = user2.post(file('acer.happy.battery.snapshot'), res=models.Snapshot) snap2, _ = user2.post(file('acer.happy.battery.snapshot'), res=models.Snapshot)
@ -1915,9 +1903,10 @@ def test_trade_case3(user: UserClient, user2: UserClient):
item='{}/devices'.format(lot['id']), item='{}/devices'.format(lot['id']),
query=devices[-1:], status=200) query=devices[-1:], status=200)
assert device2.actions[-2].t == 'Revoke' assert device2.actions[-2].t == 'Confirm'
assert device2.actions[-1].t == 'ConfirmRevoke' assert device2.actions[-1].t == 'Revoke'
assert device2.actions[-1].user == trade.user_from assert device2.actions[-1].user == trade.user_from
assert device2.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -1979,9 +1968,10 @@ def test_trade_case4(user: UserClient, user2: UserClient):
assert device1.actions[-2].t == 'Trade' assert device1.actions[-2].t == 'Trade'
assert device1.actions[-1].t == 'Confirm' assert device1.actions[-1].t == 'Confirm'
assert device1.actions[-1].user == trade.user_to assert device1.actions[-1].user == trade.user_to
assert device2.actions[-2].t == 'Revoke' assert device2.actions[-2].t == 'Confirm'
assert device2.actions[-1].t == 'ConfirmRevoke' assert device2.actions[-1].t == 'Revoke'
assert device2.actions[-1].user == trade.user_from assert device2.actions[-1].user == trade.user_from
assert device2.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2036,8 +2026,8 @@ def test_trade_case5(user: UserClient, user2: UserClient):
assert device2.actions[-1].user == trade.user_from assert device2.actions[-1].user == trade.user_from
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device2.actions[-1].id, 'action': trade.id,
'devices': [device2.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) user.post(res=models.Action, data=request_confirm_revoke)
assert device2.actions[-2].t == '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.actions[-1].user == trade.user_to
assert device2.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2106,8 +2097,8 @@ def test_trade_case6(user: UserClient, user2: UserClient):
assert device2.actions[-1].user == trade.user_to assert device2.actions[-1].user == trade.user_to
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device2.actions[-1].id, 'action': trade.id,
'devices': [device2.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) user2.post(res=models.Action, data=request_confirm_revoke)
assert device2.actions[-2].t == '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.actions[-1].user == trade.user_from
assert device2.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2158,6 +2150,7 @@ def test_trade_case7(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user2.post(res=models.Action, data=request_confirm) user2.post(res=models.Action, data=request_confirm)
assert device.trading(trade.lot) == 'TradeConfirmed'
lot, _ = user.delete({}, lot, _ = user.delete({},
res=Lot, res=Lot,
@ -2165,14 +2158,14 @@ def test_trade_case7(user: UserClient, user2: UserClient):
query=devices, status=200) query=devices, status=200)
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user2.post(res=models.Action, data=request_confirm_revoke) 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[-1].user == trade.user_from
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_to 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[-4].user == trade.user_to
assert device.actions[-5].t == 'Trade' assert device.actions[-5].t == 'Trade'
assert device.actions[-5].author == trade.user_to assert device.actions[-5].author == trade.user_to
assert device.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2223,6 +2217,7 @@ def test_trade_case8(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user2.post(res=models.Action, data=request_confirm) user2.post(res=models.Action, data=request_confirm)
assert device.trading(trade.lot) == 'TradeConfirmed'
request_revoke = { request_revoke = {
'type': 'Revoke', 'type': 'Revoke',
@ -2234,14 +2229,14 @@ def test_trade_case8(user: UserClient, user2: UserClient):
user.post(res=models.Action, data=request_revoke) user.post(res=models.Action, data=request_revoke)
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user2.post(res=models.Action, data=request_confirm_revoke) 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[-1].user == trade.user_from
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_to 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[-4].user == trade.user_to
assert device.actions[-5].t == 'Trade' assert device.actions[-5].t == 'Trade'
assert device.actions[-5].author == trade.user_to assert device.actions[-5].author == trade.user_to
assert device.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2303,6 +2299,7 @@ def test_trade_case9(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user.post(res=models.Action, data=request_confirm) user.post(res=models.Action, data=request_confirm)
assert device.trading(trade.lot) == 'TradeConfirmed'
assert device.owner == trade.user_to assert device.owner == trade.user_to
@ -2312,8 +2309,8 @@ def test_trade_case9(user: UserClient, user2: UserClient):
query=devices[-1:], status=200) query=devices[-1:], status=200)
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
@ -2321,7 +2318,7 @@ def test_trade_case9(user: UserClient, user2: UserClient):
assert device.owner == trade.user_from 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[-1].user == trade.user_to
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_from 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[-4].user == trade.user_from
assert device.actions[-5].t == 'Trade' assert device.actions[-5].t == 'Trade'
assert device.actions[-5].author == trade.user_to assert device.actions[-5].author == trade.user_to
assert device.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2374,6 +2372,7 @@ def test_trade_case10(user: UserClient, user2: UserClient):
device1, device = trade.devices device1, device = trade.devices
assert device.owner == trade.user_from assert device.owner == trade.user_from
# assert device.trading(trade.lot) == 'Confirm'
request_confirm = { request_confirm = {
'type': 'Confirm', 'type': 'Confirm',
@ -2383,6 +2382,7 @@ def test_trade_case10(user: UserClient, user2: UserClient):
# Normal confirm # Normal confirm
user.post(res=models.Action, data=request_confirm) user.post(res=models.Action, data=request_confirm)
# assert device.trading(trade.lot) == 'TradeConfirmed'
assert device.owner == trade.user_to assert device.owner == trade.user_to
@ -2394,18 +2394,18 @@ def test_trade_case10(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user2.post(res=models.Action, data=request_revoke) user2.post(res=models.Action, data=request_revoke)
assert device.trading(trade.lot) == 'Revoke'
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user.post(res=models.Action, data=request_confirm_revoke) user.post(res=models.Action, data=request_confirm_revoke)
assert device.owner == trade.user_from assert device.owner == trade.user_from
assert device.actions[-1].t == 'Revoke'
assert device.actions[-1].t == 'ConfirmRevoke'
assert device.actions[-1].user == trade.user_to assert device.actions[-1].user == trade.user_to
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_from 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[-4].user == trade.user_from
assert device.actions[-5].t == 'Trade' assert device.actions[-5].t == 'Trade'
assert device.actions[-5].author == trade.user_to assert device.actions[-5].author == trade.user_to
assert device.trading(trade.lot) == 'RevokeConfirmed'
@pytest.mark.mvp @pytest.mark.mvp
@ -2451,6 +2452,7 @@ def test_trade_case11(user: UserClient, user2: UserClient):
trade = models.Trade.query.one() trade = models.Trade.query.one()
device1, device = trade.devices device1, device = trade.devices
assert device.trading(trade.lot) == 'Confirm'
request_confirm = { request_confirm = {
'type': 'Confirm', 'type': 'Confirm',
@ -2459,21 +2461,24 @@ def test_trade_case11(user: UserClient, user2: UserClient):
} }
user2.post(res=models.Action, data=request_confirm) user2.post(res=models.Action, data=request_confirm)
assert device.trading(trade.lot) == 'TradeConfirmed'
lot, _ = user2.delete({}, lot, _ = user2.delete({},
res=Lot, res=Lot,
item='{}/devices'.format(lot['id']), item='{}/devices'.format(lot['id']),
query=devices[-1:], status=200) query=devices[-1:], status=200)
assert device.trading(trade.lot) == 'Revoke'
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user.post(res=models.Action, data=request_confirm_revoke) 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[-1].user == trade.user_to
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_from 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() trade = models.Trade.query.one()
device1, device = trade.devices device1, device = trade.devices
assert device.trading(trade.lot) == 'Confirm'
# Normal confirm # Normal confirm
request_confirm = { request_confirm = {
@ -2528,6 +2534,7 @@ def test_trade_case12(user: UserClient, user2: UserClient):
} }
user2.post(res=models.Action, data=request_confirm) user2.post(res=models.Action, data=request_confirm)
assert device.trading(trade.lot) == 'TradeConfirmed'
request_revoke = { request_revoke = {
'type': 'Revoke', 'type': 'Revoke',
@ -2537,16 +2544,18 @@ def test_trade_case12(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user2.post(res=models.Action, data=request_revoke) user2.post(res=models.Action, data=request_revoke)
assert device.trading(trade.lot) == 'Revoke'
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user.post(res=models.Action, data=request_confirm_revoke) 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[-1].user == trade.user_to
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_from assert device.actions[-2].user == trade.user_from
@ -2597,6 +2606,8 @@ def test_trade_case13(user: UserClient, user2: UserClient):
query=devices[-1:]) query=devices[-1:])
device1, device = trade.devices device1, device = trade.devices
assert device1.trading(trade.lot) == 'NeedConfirmation'
assert device.trading(trade.lot) == 'Confirm'
request_confirm = { request_confirm = {
'type': 'Confirm', 'type': 'Confirm',
@ -2605,21 +2616,26 @@ def test_trade_case13(user: UserClient, user2: UserClient):
} }
user.post(res=models.Action, data=request_confirm) user.post(res=models.Action, data=request_confirm)
assert device1.trading(trade.lot) == 'Confirm'
assert device.trading(trade.lot) == 'TradeConfirmed'
lot, _ = user.delete({}, lot, _ = user.delete({},
res=Lot, res=Lot,
item='{}/devices'.format(lot['id']), item='{}/devices'.format(lot['id']),
query=devices[-1:], status=200) query=devices[-1:], status=200)
assert device1.trading(trade.lot) == 'Confirm'
assert device.trading(trade.lot) == 'Revoke'
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user2.post(res=models.Action, data=request_confirm_revoke) 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[-1].user == trade.user_from
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_to assert device.actions[-2].user == trade.user_to
@ -2670,6 +2686,8 @@ def test_trade_case14(user: UserClient, user2: UserClient):
query=devices[-1:]) query=devices[-1:])
device1, device = trade.devices device1, device = trade.devices
assert device1.trading(trade.lot) == 'NeedConfirmation'
assert device.trading(trade.lot) == 'Confirm'
# Normal confirm # Normal confirm
request_confirm = { request_confirm = {
@ -2679,6 +2697,7 @@ def test_trade_case14(user: UserClient, user2: UserClient):
} }
user.post(res=models.Action, data=request_confirm) user.post(res=models.Action, data=request_confirm)
assert device.trading(trade.lot) == 'TradeConfirmed'
request_revoke = { request_revoke = {
'type': 'Revoke', 'type': 'Revoke',
@ -2688,16 +2707,18 @@ def test_trade_case14(user: UserClient, user2: UserClient):
# Normal revoke # Normal revoke
user.post(res=models.Action, data=request_revoke) user.post(res=models.Action, data=request_revoke)
assert device.trading(trade.lot) == 'Revoke'
request_confirm_revoke = { request_confirm_revoke = {
'type': 'ConfirmRevoke', 'type': 'Revoke',
'action': device.actions[-1].id, 'action': trade.id,
'devices': [device.id], 'devices': [device.id],
} }
user2.post(res=models.Action, data=request_confirm_revoke) 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[-1].user == trade.user_from
assert device.actions[-2].t == 'Revoke' assert device.actions[-2].t == 'Revoke'
assert device.actions[-2].user == trade.user_to assert device.actions[-2].user == trade.user_to

View File

@ -309,9 +309,10 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
# they are not the same tags though # they are not the same tags though
# tag is a transient obj and db_tag the one from the db # tag is a transient obj and db_tag the one from the db
# they have the same pk though # 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 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 @pytest.mark.mvp
@ -346,8 +347,9 @@ def test_sync_execute_register_tag_linked_same_device():
pc.tags.add(Tag(id='foo')) pc.tags.add(Tag(id='foo'))
db_pc = Sync().execute_register(pc) db_pc = Sync().execute_register(pc)
assert db_pc.id == orig_pc.id assert db_pc.id == orig_pc.id
assert len(db_pc.tags) == 1 assert len(db_pc.tags) == 2
assert next(iter(db_pc.tags)).id == 'foo' for tag in db_pc.tags:
assert tag.id in ['foo', db_pc.devicehub_id]
@pytest.mark.mvp @pytest.mark.mvp
@ -399,6 +401,7 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_get_device(user: UserClient): def test_get_device(user: UserClient):
"""Checks GETting a d.Desktop with its components.""" """Checks GETting a d.Desktop with its components."""
g.user = User.query.one()
pc = d.Desktop(model='p1mo', pc = d.Desktop(model='p1mo',
manufacturer='p1ma', manufacturer='p1ma',
serial_number='p1s', serial_number='p1s',
@ -437,6 +440,7 @@ def test_get_device(user: UserClient):
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_get_devices(app: Devicehub, user: UserClient): def test_get_devices(app: Devicehub, user: UserClient):
"""Checks GETting multiple devices.""" """Checks GETting multiple devices."""
g.user = User.query.one()
pc = d.Desktop(model='p1mo', pc = d.Desktop(model='p1mo',
manufacturer='p1ma', manufacturer='p1ma',
serial_number='p1s', serial_number='p1s',
@ -502,7 +506,8 @@ def test_get_devices_permissions(app: Devicehub, user: UserClient, user2: UserCl
@pytest.mark.mvp @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.""" """Checks GETting multiple devices."""
user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) 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, res=Lot,
item='{}/devices'.format(my_lot['id']), item='{}/devices'.format(my_lot['id']),
query=[('id', device_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' url = '/devices/?filter={"type":["Computer"]}&unassign=0'
@ -604,6 +610,7 @@ def test_device_public(user: UserClient, client: Client):
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_computer_accessory_model(user: UserClient): def test_computer_accessory_model(user: UserClient):
g.user = User.query.one()
sai = d.SAI(owner_id=user.user['id']) sai = d.SAI(owner_id=user.user['id'])
db.session.add(sai) db.session.add(sai)
keyboard = d.Keyboard(layout=Layouts.ES, owner_id=user.user['id']) 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.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_networking_model(user: UserClient): def test_networking_model(user: UserClient):
g.user = User.query.one()
router = d.Router(speed=1000, wireless=True, owner_id=user.user['id']) router = d.Router(speed=1000, wireless=True, owner_id=user.user['id'])
db.session.add(router) db.session.add(router)
switch = d.Switch(speed=1000, wireless=False, owner_id=user.user['id']) switch = d.Switch(speed=1000, wireless=False, owner_id=user.user['id'])

View File

@ -183,7 +183,7 @@ def test_device_query(user: UserClient):
pc = next(d for d in i['items'] if d['type'] == 'Desktop') pc = next(d for d in i['items'] if d['type'] == 'Desktop')
assert len(pc['actions']) == 4 assert len(pc['actions']) == 4
assert len(pc['components']) == 3 assert len(pc['components']) == 3
assert not pc['tags'] assert pc['tags'][0]['id'] == pc['devicehubID']
@pytest.mark.mvp @pytest.mark.mvp

View File

@ -251,13 +251,13 @@ def test_check_insert_hash(app: Devicehub, user: UserClient, client: Client):
assert ReportHash.query.filter_by(hash3=hash3).count() == 1 assert ReportHash.query.filter_by(hash3=hash3).count() == 1
result, status = client.get(res=documents.DocumentDef.t, item='check/', query=[('hash', hash3)]) result, status = client.get(res=documents.DocumentDef.t, item='check/', query=[('hash', hash3)])
assert status.status_code == 200 assert status.status_code == 200
assert result == True assert result
ff = open('/tmp/test.csv', 'w') ff = open('/tmp/test.csv', 'w')
ff.write(csv_str) ff.write(csv_str)
ff.close() ff.close()
a= open('/tmp/test.csv').read() a = open('/tmp/test.csv').read()
assert hash3 == hashlib.sha3_256(a.encode('utf-8')).hexdigest() 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) snapshot2, _ = user.post(file('complete.export.snapshot'), res=Snapshot, status=201)
with app.app_context(): with app.app_context():
# Create a pc with a tag # 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 = d.Device.query.filter_by(id=snapshot1['device']['id']).first()
pc.tags.add(tag)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()

View File

@ -313,7 +313,6 @@ def test_post_get_lot(user: UserClient):
assert l['name'] == 'Foo' assert l['name'] == 'Foo'
l, _ = user.get(res=Lot, item=l['id']) l, _ = user.get(res=Lot, item=l['id'])
assert l['name'] == 'Foo' assert l['name'] == 'Foo'
assert not l['children']
def test_lot_post_add_children_view_ui_tree_normal(user: UserClient): 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 @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 """Tests adding a device to a lot using POST and
removing it with DELETE. removing it with DELETE.
""" """
# todo check with components # todo check with components
with app.app_context(): g.user = User.query.one()
device = Desktop(serial_number='foo', device = Desktop(serial_number='foo',
model='bar', model='bar',
manufacturer='foobar', manufacturer='foobar',
chassis=ComputerChassis.Lunchbox, chassis=ComputerChassis.Lunchbox,
owner_id=user.user['id']) owner_id=user.user['id'])
db.session.add(device) db.session.add(device)
db.session.commit() db.session.commit()
device_id = device.id device_id = device.id
devicehub_id = device.devicehub_id devicehub_id = device.devicehub_id
parent, _ = user.post(({'name': 'lot'}), res=Lot) parent, _ = user.post(({'name': 'lot'}), res=Lot)
lot, _ = user.post({}, lot, _ = user.post({},
res=Lot, res=Lot,
item='{}/devices'.format(parent['id']), item='{}/devices'.format(parent['id']),
query=[('id', device_id)]) query=[('id', device_id)])
assert lot['devices'][0]['id'] == device_id, 'Lot contains device' lot = Lot.query.filter_by(id=lot['id']).one()
device, _ = user.get(res=Device, item=devicehub_id) assert list(lot.devices)[0].id == device_id, 'Lot contains device'
assert len(device['lots']) == 1 device = Device.query.filter_by(devicehub_id=devicehub_id).one()
assert device['lots'][0]['id'] == lot['id'], 'Device is inside lot' 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 # Remove the device
lot, _ = user.delete(res=Lot, user.delete(res=Lot,
item='{}/devices'.format(parent['id']), item='{}/devices'.format(parent['id']),
query=[('id', device_id)], query=[('id', device_id)],
status=200) status=200)
assert not len(lot['devices']) assert not len(lot.devices)
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_lot_error_add_device_from_other_user(user: UserClient): def test_lot_error_add_device_from_other_user(user: UserClient):
# TODO
"""Tests adding a device to a lot using POST and """Tests adding a device to a lot using POST and
removing it with DELETE. removing it with DELETE.
""" """
g.user = User.query.one()
user2 = User(email='baz@baz.cxm', password='baz') user2 = User(email='baz@baz.cxm', password='baz')
user2.individuals.add(Person(name='Tommy')) user2.individuals.add(Person(name='Tommy'))
db.session.add(user2) db.session.add(user2)
@ -413,8 +417,9 @@ def test_lot_error_add_device_from_other_user(user: UserClient):
res=Lot, res=Lot,
item='{}/devices'.format(parent['id']), item='{}/devices'.format(parent['id']),
query=[('id', device_id)]) query=[('id', device_id)])
assert lot['devices'] == [], 'Lot contains device' lot = Lot.query.filter_by(id=lot['id']).one()
assert len(lot['devices']) == 0 assert list(lot.devices) == [], 'Lot contains device'
assert len(lot.devices) == 0
@pytest.mark.mvp @pytest.mark.mvp

View File

@ -2,6 +2,9 @@ import pytest
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.action import models as ma 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 import conftest
from tests.conftest import file, yaml2json, json_encode from tests.conftest import file, yaml2json, json_encode
@ -20,8 +23,7 @@ def test_simple_metrics(user: UserClient):
"finalUserCode": "abcdefjhi", "finalUserCode": "abcdefjhi",
"devices": [device_id], "description": "aaa", "devices": [device_id], "description": "aaa",
"startTime": "2020-11-01T02:00:00+00:00", "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 # Create Allocate
user.post(res=ma.Allocate, data=post_request) user.post(res=ma.Allocate, data=post_request)
@ -65,8 +67,7 @@ def test_second_hdd_metrics(user: UserClient):
"finalUserCode": "abcdefjhi", "finalUserCode": "abcdefjhi",
"devices": [device_id], "description": "aaa", "devices": [device_id], "description": "aaa",
"startTime": "2020-11-01T02:00:00+00:00", "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 # Create Allocate
user.post(res=ma.Allocate, data=post_request) user.post(res=ma.Allocate, data=post_request)
@ -109,8 +110,7 @@ def test_metrics_with_live_null(user: UserClient):
"finalUserCode": "abcdefjhi", "finalUserCode": "abcdefjhi",
"devices": [device_id], "description": "aaa", "devices": [device_id], "description": "aaa",
"startTime": "2020-11-01T02:00:00+00:00", "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 # Create Allocate
user.post(res=ma.Allocate, data=post_request) user.post(res=ma.Allocate, data=post_request)
@ -120,3 +120,240 @@ def test_metrics_with_live_null(user: UserClient):
res, _ = user.get("/metrics/") res, _ = user.get("/metrics/")
assert res == 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

View File

@ -37,7 +37,6 @@ from tests import conftest
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures('auth_app_context') @pytest.mark.usefixtures('auth_app_context')
# cayop
def test_snapshot_model(): def test_snapshot_model():
"""Tests creating a Snapshot with its relationships ensuring correct """Tests creating a Snapshot with its relationships ensuring correct
DB mapping. DB mapping.
@ -318,7 +317,7 @@ def test_snapshot_tag_inner_tag(user: UserClient, tag_id: str, app: Devicehub):
snapshot_and_check(user, b, snapshot_and_check(user, b,
action_types=(RateComputer.t, BenchmarkProcessor.t, VisualTest.t)) action_types=(RateComputer.t, BenchmarkProcessor.t, VisualTest.t))
with app.app_context(): 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' 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): def test_snapshot_upload_twice_uuid_error(user: UserClient):
pc1 = file('basic.snapshot') pc1 = file('basic.snapshot')
user.post(pc1, res=Snapshot) user.post(pc1, res=Snapshot)
user.post(pc1, res=Snapshot, status=UniqueViolation) user.post(pc1, res=Snapshot, status=400)
@pytest.mark.mvp @pytest.mark.mvp

View File

@ -2,6 +2,7 @@ import pathlib
import pytest import pytest
import requests_mock import requests_mock
from flask import g
from boltons.urlutils import URL from boltons.urlutils import URL
from ereuse_utils.session import DevicehubClient from ereuse_utils.session import DevicehubClient
from pytest import raises from pytest import raises
@ -11,6 +12,7 @@ from teal.marshmallow import ValidationError
from ereuse_devicehub.client import UserClient, Client from ereuse_devicehub.client import UserClient, Client
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.devicehub import Devicehub 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.action.models import Snapshot
from ereuse_devicehub.resources.agent.models import Organization from ereuse_devicehub.resources.agent.models import Organization
from ereuse_devicehub.resources.device.models import Desktop, Device 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, \ from ereuse_devicehub.resources.tag.view import CannotCreateETag, LinkedToAnotherDevice, \
TagNotLinked TagNotLinked
from tests import conftest from tests import conftest
from tests.conftest import file, yaml2json, json_encode from tests.conftest import yaml2json, json_encode
@pytest.mark.mvp @pytest.mark.mvp
@ -41,6 +43,7 @@ def test_create_tag(user: UserClient):
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_create_tag_with_device(user: UserClient): def test_create_tag_with_device(user: UserClient):
"""Creates a tag specifying linked with one device.""" """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']) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id'])
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -60,13 +63,14 @@ def test_create_tag_with_device(user: UserClient):
def test_delete_tags(user: UserClient, client: Client): def test_delete_tags(user: UserClient, client: Client):
"""Delete a named tag.""" """Delete a named tag."""
# Delete Tag Named # Delete Tag Named
g.user = User.query.one()
pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id'])
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
tag = Tag(id='bar', owner_id=user.user['id'], device_id=pc.id) tag = Tag(id='bar', owner_id=user.user['id'], device_id=pc.id)
db.session.add(tag) db.session.add(tag)
db.session.commit() db.session.commit()
tag = Tag.query.one() tag = Tag.query.all()[-1]
assert tag.id == 'bar' assert tag.id == 'bar'
# Is not possible delete one tag linked to one device # Is not possible delete one tag linked to one device
res, _ = user.delete(res=Tag, item=tag.id, status=422) 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']) tag = Tag(id='bar-1', org=org, provider=URL('http://foo.bar'), owner_id=user.user['id'])
db.session.add(tag) db.session.add(tag)
db.session.commit() db.session.commit()
tag = Tag.query.one() tag = Tag.query.all()[-1]
assert tag.id == 'bar-1' assert tag.id == 'bar-1'
res, _ = user.delete(res=Tag, item=tag.id, status=422) res, _ = user.delete(res=Tag, item=tag.id, status=422)
msg = 'This tag {} is unnamed tag. It is imposible delete.'.format(tag.id) msg = 'This tag {} is unnamed tag. It is imposible delete.'.format(tag.id)
assert msg in res['message'] assert msg in res['message']
tag = Tag.query.one() tag = Tag.query.all()[-1]
assert tag.id == 'bar-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""" """Checks getting a linked device from a tag endpoint"""
with app.app_context(): with app.app_context():
# Create a pc with a tag # Create a pc with a tag
g.user = User.query.one()
tag = Tag(id='foo-bar', owner_id=user.user['id']) tag = Tag(id='foo-bar', owner_id=user.user['id'])
pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id'])
pc.tags.add(tag) 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 system should not return any of both (to be deterministic) so
it should raise an exception. 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.add(Tag(id='foo', secondary='bar', owner_id=user.user['id']))
db.session.commit() db.session.commit()
@ -276,6 +282,7 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient):
Checks search has the term. Checks search has the term.
""" """
with app.app_context(): with app.app_context():
g.user = User.query.one()
db.session.add(Tag('foo-bar', secondary='foo-sec', owner_id=user.user['id'])) 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']) desktop = Desktop(serial_number='foo', chassis=ComputerChassis.AllInOne, owner_id=user.user['id'])
db.session.add(desktop) db.session.add(desktop)
@ -284,7 +291,7 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient):
devicehub_id = desktop.devicehub_id devicehub_id = desktop.devicehub_id
user.put({}, res=Tag, item='foo-bar/device/{}'.format(desktop_id), status=204) user.put({}, res=Tag, item='foo-bar/device/{}'.format(desktop_id), status=204)
device, _ = user.get(res=Device, item=devicehub_id) 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 # Device already linked
# Just returns an OK to conform to PUT as anything changes # 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'}] s['device']['tags'] = [{'id': 'foo', 'secondary': 'bar', 'type': 'Tag'}]
snapshot, _ = user.post(json_encode(s), res=Snapshot) snapshot, _ = user.post(json_encode(s), res=Snapshot)
device, _ = user.get(res=Device, item=snapshot['device']['devicehubID']) device, _ = user.get(res=Device, item=snapshot['device']['devicehubID'])
assert device['tags'][0]['id'] == 'foo' assert 'foo' in [x['id'] for x in device['tags']]
assert device['tags'][0]['secondary'] == 'bar' assert 'bar' in [x.get('secondary') for x in device['tags']]
r, _ = user.get(res=Device, query=[('search', 'foo'), ('filter', {'type': ['Computer']})]) r, _ = user.get(res=Device, query=[('search', 'foo'), ('filter', {'type': ['Computer']})])
assert len(r['items']) == 1 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.""" """Creates a tag specifying a custom organization."""
with app.app_context(): with app.app_context():
# Create a pc with a tag # Create a pc with a tag
g.user = User.query.all()[0]
tag = Tag(id='foo-bar', owner_id=user.user['id']) tag = Tag(id='foo-bar', owner_id=user.user['id'])
pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id']) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id'])
pc.tags.add(tag) 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) computer2, res2 = user2.get(url, None)
assert res.status_code == 200 assert res.status_code == 200
assert res2.status_code == 200 assert res2.status_code == 200
assert len(computer['items']) == 1 assert len(computer['items']) == 2
assert len(computer2['items']) == 0 assert len(computer2['items']) == 0

View File

@ -66,8 +66,8 @@ def test_workbench_server_condensed(user: UserClient):
assert device['rate']['rating'] == 1 assert device['rate']['rating'] == 1
assert device['rate']['type'] == RateComputer.t assert device['rate']['type'] == RateComputer.t
# TODO JN why haven't same order in actions on each execution? # 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 any([ac['type'] in [BenchmarkProcessor.t, BenchmarkRamSysbench.t] for ac in device['actions']])
assert device['tags'][0]['id'] == 'tag1' assert 'tag1' in [x['id'] for x in device['tags']]
@pytest.mark.xfail(reason='Functionality not yet developed.') @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['serialNumber'] == 'b8oaas048286'
assert pc['manufacturer'] == 'asustek computer inc.' assert pc['manufacturer'] == 'asustek computer inc.'
assert pc['hid'] == 'laptop-asustek_computer_inc-1001pxd-b8oaas048286-14:da:e9:42:f6:7c' 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['networkSpeeds'] == [100, 0], 'Although it has WiFi we do not know the speed'
assert pc['rate'] assert pc['rate']
rate = pc['rate'] rate = pc['rate']