Merge branch 'testing' into feature/3729-user-registration

This commit is contained in:
Cayo Puigdefabregas 2022-10-18 17:15:28 +02:00
commit c2d81b1b29
46 changed files with 1428 additions and 561 deletions

View File

@ -1,10 +1,8 @@
name: Selenium name: Selenium
on: on:
push:
branches: [master, testing]
pull_request: pull_request:
branches: [master, testing] types: [ready_for_review, review_requested]
jobs: jobs:
build: build:

View File

@ -19,5 +19,13 @@ repos:
hooks: hooks:
- id: build-js - id: build-js
name: build-js name: build-js
# pre-commit pass as parameters files included on the commit
# so babel command should be wrapped to ignore these files on
# package.json script
entry: npm run babel entry: npm run babel
language: node language: node
files: ^ereuse_devicehub/static/js/main_inventory.js
- repo: https://github.com/jazzband/pip-tools
rev: 6.8.0
hooks:
- id: pip-compile

View File

@ -7,6 +7,31 @@ ml).
## testing ## testing
## [2.4.2] - 2022-10-18
- [added] #373 Enhancement - UX Lots.
- [added] #377 add prefix in lots in device list.
- [added] #378 add new button transfer.
- [added] #381 add servers erase and show storage disk in list of device.
- [added] #383 new setup page and add server_erase in placeholder.
- [added] #384 add redirect snapshot to twin public page.
- [changed] #371 changes phid.
- [changed] #372 remove logo.
- [changed] #374 changes links UI management and Data Storage Erasure.
- [changed] #375 changes columns in snapshot logs.
- [changed] #379 changes representation date times.
- [fixed] #380 fix layout print label.
- [fixed] #382 fix template device list.
- [fixed] #385 components in unbinding process.
## [2.4.1] - 2022-10-05
- [added] #365 Manage dependencies using pip-tools.
- [added] #368 add migrations of monitors and mobiles.
- [changed]] #371 changes about phid, incremental per user.
- [fixed] #364 bad redirect to all devices.
- [fixed] #367 column PHID Erasure host.
- [fixed] #369 bug in test data storage.
- [fixed] #370 print label in details of the label.
## [2.4.0] - 2022-09-23 ## [2.4.0] - 2022-09-23
- [added] #312 Placeholder: new, edit, update. (manually and with excel). - [added] #312 Placeholder: new, edit, update. (manually and with excel).
- [added] #316 Placeholder: binding/unbinding. (manually). - [added] #316 Placeholder: binding/unbinding. (manually).

View File

@ -30,3 +30,29 @@ pre-commit install
Do this: `device_detail.html` Do this: `device_detail.html`
Don't do this: `DeviceDetail.html`, `Device-detail.html` Don't do this: `DeviceDetail.html`, `Device-detail.html`
## Adding a new dependency to the project
This project tracks its packages using pip-tools, it could be installed by running:
```
pip install pip-tools
```
Whenever you need to install a new package using pip install <package-name>:
1. Put the package name into `requirements.in` instead.
```
# requirements.in
...
new_package
```
2. Compile the requirements
```
pip-compile requirements.in --output-file=requirements.txt
```
3. Then install upgraded dependencies:
```
pip install -U -r requirements.txt
```

View File

@ -1 +1 @@
__version__ = "2.4.0" __version__ = "2.4.2"

View File

@ -86,12 +86,18 @@ DEVICES = {
"Smartphone", "Smartphone",
"Cellphone", "Cellphone",
], ],
"Drives & Storage": [
"All DataStorage",
"HardDrives",
"SolidStageDrive",
],
} }
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer'] COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"] MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"] MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"]
STORAGE = ["HardDrive", "SolidStateDrive"]
class AdvancedSearchForm(FlaskForm): class AdvancedSearchForm(FlaskForm):
@ -175,9 +181,16 @@ class FilterForm(FlaskForm):
elif "All Mobile" == self.device_type: elif "All Mobile" == self.device_type:
filter_type = MOBILE filter_type = MOBILE
elif "All DataStorage" == self.device_type:
filter_type = STORAGE
if filter_type: if filter_type:
self.devices = self.devices.filter(Device.type.in_(filter_type)) self.devices = self.devices.filter(Device.type.in_(filter_type))
# if self.device_type in STORAGE + ["All DataStorage"]:
# import pdb; pdb.set_trace()
# self.devices = self.devices.filter(Component.parent_id.is_(None))
return self.devices.order_by(Device.updated.desc()) return self.devices.order_by(Device.updated.desc())
@ -322,7 +335,7 @@ class NewDeviceForm(FlaskForm):
default=1, default=1,
) )
id_device_supplier = StringField('Id Supplier', [validators.Optional()]) id_device_supplier = StringField('Id Supplier', [validators.Optional()])
phid = StringField('Placeholder Hardware identity (Phid)', [validators.Optional()]) id_device_internal = StringField('Id Internal', [validators.Optional()])
pallet = StringField('Identity of pallet', [validators.Optional()]) pallet = StringField('Identity of pallet', [validators.Optional()])
components = TextAreaField('Components', [validators.Optional()]) components = TextAreaField('Components', [validators.Optional()])
info = TextAreaField('Info', [validators.Optional()]) info = TextAreaField('Info', [validators.Optional()])
@ -382,7 +395,7 @@ class NewDeviceForm(FlaskForm):
self.type.data = self._obj.type self.type.data = self._obj.type
self.amount.render_kw = disabled self.amount.render_kw = disabled
self.id_device_supplier.data = self._obj.placeholder.id_device_supplier self.id_device_supplier.data = self._obj.placeholder.id_device_supplier
self.phid.data = self._obj.placeholder.phid self.id_device_internal.data = self._obj.placeholder.id_device_internal
self.pallet.data = self._obj.placeholder.pallet self.pallet.data = self._obj.placeholder.pallet
self.info.data = self._obj.placeholder.info self.info.data = self._obj.placeholder.info
self.components.data = self._obj.placeholder.components self.components.data = self._obj.placeholder.components
@ -411,7 +424,7 @@ class NewDeviceForm(FlaskForm):
if self._obj.placeholder.is_abstract: if self._obj.placeholder.is_abstract:
self.type.render_kw = disabled self.type.render_kw = disabled
self.amount.render_kw = disabled self.amount.render_kw = disabled
self.id_device_supplier.render_kw = disabled # self.id_device_supplier.render_kw = disabled
self.pallet.render_kw = disabled self.pallet.render_kw = disabled
self.info.render_kw = disabled self.info.render_kw = disabled
self.components.render_kw = disabled self.components.render_kw = disabled
@ -476,28 +489,6 @@ class NewDeviceForm(FlaskForm):
self.meid.errors = error self.meid.errors = error
is_valid = False is_valid = False
if self.phid.data and self.amount.data == 1 and not self._obj:
dev = Placeholder.query.filter(
Placeholder.phid == self.phid.data, Placeholder.owner == g.user
).first()
if dev:
msg = "Error, exist one Placeholder device with this PHID"
self.phid.errors = [msg]
is_valid = False
if (
self.phid.data
and self._obj
and self.phid.data != self._obj.placeholder.phid
):
dev = Placeholder.query.filter(
Placeholder.phid == self.phid.data, Device.owner == g.user
).first()
if dev:
msg = "Error, exist one Placeholder device with this PHID"
self.phid.errors = [msg]
is_valid = False
if not is_valid: if not is_valid:
return False return False
@ -579,8 +570,8 @@ class NewDeviceForm(FlaskForm):
def reset_ids(self): def reset_ids(self):
if self.amount.data > 1: if self.amount.data > 1:
self.phid.data = None
self.id_device_supplier.data = None self.id_device_supplier.data = None
self.id_device_internal.data = None
self.serial_number.data = None self.serial_number.data = None
self.part_number.data = None self.part_number.data = None
self.sku.data = None self.sku.data = None
@ -590,8 +581,8 @@ class NewDeviceForm(FlaskForm):
def get_placeholder(self): def get_placeholder(self):
self.placeholder = Placeholder( self.placeholder = Placeholder(
**{ **{
'phid': self.phid.data or None,
'id_device_supplier': self.id_device_supplier.data, 'id_device_supplier': self.id_device_supplier.data,
'id_device_internal': self.id_device_internal.data,
'info': self.info.data, 'info': self.info.data,
'components': self.components.data, 'components': self.components.data,
'pallet': self.pallet.data, 'pallet': self.pallet.data,
@ -601,11 +592,13 @@ class NewDeviceForm(FlaskForm):
return self.placeholder return self.placeholder
def edit_device(self): def edit_device(self):
self._obj.placeholder.phid = self.phid.data or self._obj.placeholder.phid
if not self._obj.placeholder.is_abstract: if not self._obj.placeholder.is_abstract:
self._obj.placeholder.id_device_supplier = ( self._obj.placeholder.id_device_supplier = (
self.id_device_supplier.data or None self.id_device_supplier.data or None
) )
self._obj.placeholder.id_device_internal = (
self.id_device_internal.data or None
)
self._obj.placeholder.info = self.info.data or None self._obj.placeholder.info = self.info.data or None
self._obj.placeholder.components = self.components.data or None self._obj.placeholder.components = self.components.data or None
self._obj.placeholder.pallet = self.pallet.data or None self._obj.placeholder.pallet = self.pallet.data or None
@ -1279,6 +1272,12 @@ class TradeDocumentForm(FlaskForm):
class TransferForm(FlaskForm): class TransferForm(FlaskForm):
lot_name = StringField(
'Lot Name',
[validators.DataRequired()],
render_kw={'class': "form-control"},
description="You need put a lot name",
)
code = StringField( code = StringField(
'Code', 'Code',
[validators.DataRequired()], [validators.DataRequired()],
@ -1295,14 +1294,14 @@ class TransferForm(FlaskForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._type = kwargs.get('type') self._type = kwargs.get('type')
lot_id = kwargs.pop('lot_id', None) lot_id = kwargs.pop('lot_id', None)
self._tmp_lot = Lot.query.filter(Lot.id == lot_id).one() self._tmp_lot = None
if lot_id:
self._tmp_lot = Lot.query.filter(Lot.id == lot_id).one()
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._obj = None self._obj = None
def validate(self, extra_validators=None): def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators) is_valid = super().validate(extra_validators)
if not self._tmp_lot:
return False
if self._type and self.type.data not in ['incoming', 'outgoing']: if self._type and self.type.data not in ['incoming', 'outgoing']:
return False return False
@ -1323,8 +1322,10 @@ class TransferForm(FlaskForm):
return self._obj return self._obj
def set_obj(self): def set_obj(self):
self.newlot = Lot(name=self._tmp_lot.name) name = self.lot_name.data
self.newlot.devices = self._tmp_lot.devices self.newlot = Lot(name=name)
if self._tmp_lot:
self.newlot.devices = self._tmp_lot.devices
db.session.add(self.newlot) db.session.add(self.newlot)
self._obj = Transfer(lot=self.newlot) self._obj = Transfer(lot=self.newlot)
@ -1355,6 +1356,7 @@ class EditTransferForm(TransferForm):
self.code.data = self._obj.code self.code.data = self._obj.code
self.description.data = self._obj.description self.description.data = self._obj.description
self.date.data = self._obj.date self.date.data = self._obj.date
self.lot_name.data = self._obj.lot.name
def validate(self, extra_validators=None): def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators) is_valid = super().validate(extra_validators)
@ -1366,6 +1368,7 @@ class EditTransferForm(TransferForm):
def set_obj(self, commit=True): def set_obj(self, commit=True):
self.populate_obj(self._obj) self.populate_obj(self._obj)
self._obj.lot.name = self.lot_name.data
class NotesForm(FlaskForm): class NotesForm(FlaskForm):
@ -1514,9 +1517,7 @@ class UploadPlaceholderForm(FlaskForm):
else: else:
self.source = "Excel File: {}".format(_file.filename) self.source = "Excel File: {}".format(_file.filename)
try: try:
data = ( data = pd.read_excel(_file).fillna('').to_dict()
pd.read_excel(_file, converters={'Phid': str}).fillna('').to_dict()
)
except ValueError: except ValueError:
txt = ["File don't have a correct format"] txt = ["File don't have a correct format"]
self.placeholder_file.errors = txt self.placeholder_file.errors = txt
@ -1538,12 +1539,12 @@ class UploadPlaceholderForm(FlaskForm):
return False return False
header = [ header = [
'Phid',
'Model', 'Model',
'Manufacturer', 'Manufacturer',
'Serial Number', 'Serial Number',
'Part Number', 'Part Number',
'Id device Supplier', 'Id device Supplier',
'Id device Internal',
'Pallet', 'Pallet',
'Info', 'Info',
] ]
@ -1557,32 +1558,7 @@ class UploadPlaceholderForm(FlaskForm):
self.placeholders = [] self.placeholders = []
schema = SnapshotSchema() schema = SnapshotSchema()
self.path_snapshots = {} self.path_snapshots = {}
for i in data['Phid'].keys(): for i in data['Model'].keys():
placeholder = None
data['Phid'][i] = str(data['Phid'][i])
if data['Phid'][i]:
placeholder = Placeholder.query.filter_by(phid=data['Phid'][i]).first()
# update one
if placeholder:
self.dev_update += 1
device = placeholder.device
device.model = "{}".format(data['Model'][i]).lower()
device.manufacturer = "{}".format(data['Manufacturer'][i]).lower()
device.serial_number = "{}".format(data['Serial Number'][i]).lower()
device.part_number = "{}".format(data['Part Number'][i]).lower()
placeholder.id_device_supplier = "{}".format(
data['Id device Supplier'][i]
)
placeholder.pallet = "{}".format(data['Pallet'][i])
placeholder.info = "{}".format(data['Info'][i])
placeholder_log = PlaceholdersLog(
type="Update", source=self.source, placeholder=device.placeholder
)
self.placeholders.append((device, placeholder_log))
continue
# create a new one # create a new one
json_snapshot = { json_snapshot = {
'type': 'Snapshot', 'type': 'Snapshot',
@ -1597,8 +1573,8 @@ class UploadPlaceholderForm(FlaskForm):
}, },
} }
json_placeholder = { json_placeholder = {
'phid': data['Phid'][i] or None,
'id_device_supplier': data['Id device Supplier'][i], 'id_device_supplier': data['Id device Supplier'][i],
'id_device_internal': data['Id device Internal'][i],
'pallet': data['Pallet'][i], 'pallet': data['Pallet'][i],
'info': data['Info'][i], 'info': data['Info'][i],
'is_abstract': False, 'is_abstract': False,
@ -1635,7 +1611,6 @@ class EditPlaceholderForm(FlaskForm):
serial_number = StringField('Serial Number', [validators.Optional()]) serial_number = StringField('Serial Number', [validators.Optional()])
part_number = StringField('Part Number', [validators.Optional()]) part_number = StringField('Part Number', [validators.Optional()])
id_device_supplier = StringField('Id Supplier', [validators.Optional()]) id_device_supplier = StringField('Id Supplier', [validators.Optional()])
phid = StringField('Phid', [validators.DataRequired()])
pallet = StringField('Pallet', [validators.Optional()]) pallet = StringField('Pallet', [validators.Optional()])
info = StringField('Info', [validators.Optional()]) info = StringField('Info', [validators.Optional()])

View File

@ -296,6 +296,8 @@ class BindingView(GenericMixin):
self.real_phid = self.new_placeholder.phid self.real_phid = self.new_placeholder.phid
self.abstract_dhid = self.old_device.devicehub_id self.abstract_dhid = self.old_device.devicehub_id
self.abstract_phid = self.old_placeholder.phid self.abstract_phid = self.old_placeholder.phid
if self.old_placeholder.kangaroo:
self.new_placeholder.kangaroo = True
# to do a backup of abstract_dhid and abstract_phid in # to do a backup of abstract_dhid and abstract_phid in
# workbench device # workbench device
@ -380,9 +382,14 @@ class UnBindingView(GenericMixin):
return flask.render_template(self.template_name, **self.context) return flask.render_template(self.template_name, **self.context)
def clone_device(self, device): def clone_device(self, device):
if device.binding.is_abstract: if device.binding and device.binding.is_abstract:
return return
kangaroo = False
if device.binding:
kangaroo = device.binding.kangaroo
device.binding.kangaroo = False
dict_device = copy.copy(device.__dict__) dict_device = copy.copy(device.__dict__)
dict_device.pop('_sa_instance_state') dict_device.pop('_sa_instance_state')
dict_device.pop('id', None) dict_device.pop('id', None)
@ -401,8 +408,14 @@ class UnBindingView(GenericMixin):
for c in device.components: for c in device.components:
if c.binding: if c.binding:
c.binding.device.parent = new_device c.binding.device.parent = new_device
else:
new_c = self.clone_device(c)
new_c.parent = new_device
placeholder = Placeholder(
device=new_device, binding=device, is_abstract=True, kangaroo=kangaroo
)
placeholder = Placeholder(device=new_device, binding=device, is_abstract=True)
if ( if (
device.dhid_bk device.dhid_bk
and not Device.query.filter_by(devicehub_id=device.dhid_bk).first() and not Device.query.filter_by(devicehub_id=device.dhid_bk).first()
@ -545,7 +558,7 @@ class DeviceCreateView(GenericMixin):
tpy = form.type.data tpy = form.type.data
txt = f'{amount} placeholders Device "{tpy}" created successfully.' txt = f'{amount} placeholders Device "{tpy}" created successfully.'
placeholder = ( placeholder = (
Placeholder.query.filter_by(owner=g.user) Placeholder.query.filter(Placeholder.owner == g.user)
.order_by(Placeholder.id.desc()) .order_by(Placeholder.id.desc())
.first() .first()
) )
@ -753,9 +766,11 @@ class NewTransferView(GenericMixin):
form_class = TransferForm form_class = TransferForm
title = "Add new transfer" title = "Add new transfer"
def dispatch_request(self, lot_id, type_id): def dispatch_request(self, type_id, lot_id=None):
self.form = self.form_class(lot_id=lot_id, type=type_id) self.form = self.form_class(lot_id=lot_id, type=type_id)
self.get_context() self.get_context()
referrer = request.referrer or url_for('inventory.devicelist')
self.context.update({'referrer': referrer})
if self.form.validate_on_submit(): if self.form.validate_on_submit():
self.form.save() self.form.save()
@ -767,7 +782,12 @@ class NewTransferView(GenericMixin):
next_url = url_for('inventory.lotdevicelist', lot_id=str(new_lot_id)) next_url = url_for('inventory.lotdevicelist', lot_id=str(new_lot_id))
return flask.redirect(next_url) return flask.redirect(next_url)
self.context.update({'form': self.form, 'title': self.title}) self.context.update(
{
'form': self.form,
'title': self.title,
}
)
return flask.render_template(self.template_name, **self.context) return flask.render_template(self.template_name, **self.context)
@ -1355,6 +1375,10 @@ devices.add_url_rule(
) )
devices.add_url_rule( devices.add_url_rule(
'/lot/<string:lot_id>/transfer/<string:type_id>/', '/lot/<string:lot_id>/transfer/<string:type_id>/',
view_func=NewTransferView.as_view('lot_new_transfer'),
)
devices.add_url_rule(
'/lot/transfer/<string:type_id>/',
view_func=NewTransferView.as_view('new_transfer'), view_func=NewTransferView.as_view('new_transfer'),
) )
devices.add_url_rule( devices.add_url_rule(

View File

@ -114,21 +114,35 @@ class PrintLabelsView(View):
class LabelDetailView(View): class LabelDetailView(View):
"""This View is used to print labels from multiple devices"""
methods = ['POST', 'GET']
decorators = [login_required] decorators = [login_required]
template_name = 'labels/label_detail.html' template_name = 'labels/print_labels.html'
title = 'Design and implementation of labels'
def dispatch_request(self, id): def dispatch_request(self, id):
lots = Lot.query.filter(Lot.owner_id == current_user.id) lots = Lot.query.filter(Lot.owner_id == current_user.id)
tag = ( tag = (
Tag.query.filter(Tag.owner_id == current_user.id).filter(Tag.id == id).one() Tag.query.filter(Tag.owner_id == current_user.id).filter(Tag.id == id).one()
) )
context = { context = {
'lots': lots, 'lots': lots,
'tag': tag, 'page_title': self.title,
'page_title': '{} Tag'.format(tag.code),
'version': __version__, 'version': __version__,
'referrer': request.referrer,
} }
devices = []
if tag.device:
form = PrintLabelsForm(devices=str(tag.device.id))
devices = [tag.device]
else:
form = PrintLabelsForm()
form._devices = devices
context['form'] = form
context['devices'] = devices
return flask.render_template(self.template_name, **context) return flask.render_template(self.template_name, **context)

View File

@ -0,0 +1,59 @@
"""id internal in placeholder
Revision ID: 626c17026ca7
Revises: e919fe0611ff
Create Date: 2022-10-03 19:25:00.581699
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = '626c17026ca7'
down_revision = 'e919fe0611ff'
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_datas():
con = op.get_bind()
sql = 'select id from common.user where phantom=false and active=true'
users = con.execute(sql)
for user in users:
phid = 1
user_id = user.id
sql = f"""
select id from {get_inv()}.placeholder where owner_id='{user_id}'
order by id
"""
placeholders = con.execute(sql)
for p in placeholders:
p_id = p.id
sql = f"""
update {get_inv()}.placeholder set phid='{phid}'
where id='{p_id}'
"""
con.execute(sql)
phid += 1
def upgrade():
op.add_column(
'placeholder',
sa.Column('id_device_internal', sa.Unicode(), nullable=True),
schema=f'{get_inv()}',
)
upgrade_datas()
def downgrade():
op.drop_column('placeholder', 'id_device_internal', schema=f'{get_inv()}')

View File

@ -0,0 +1,34 @@
"""add kangaroo in placeholder
Revision ID: a13ed6ad0e3e
Revises: 626c17026ca7
Create Date: 2022-10-13 11:56:15.303218
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = 'a13ed6ad0e3e'
down_revision = '626c17026ca7'
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(
'placeholder',
sa.Column('kangaroo', sa.Boolean(), nullable=True),
schema=f'{get_inv()}',
)
def downgrade():
op.drop_column('placeholder', 'kangaroo', schema=f'{get_inv()}')

View File

@ -0,0 +1,34 @@
"""add is_server_erase
Revision ID: d65745749e34
Revises: a13ed6ad0e3e
Create Date: 2022-10-17 13:20:29.875274
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = 'd65745749e34'
down_revision = 'a13ed6ad0e3e'
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(
'snapshot',
sa.Column('is_server_erase', sa.Boolean(), nullable=True),
schema=f'{get_inv()}',
)
def downgrade():
op.drop_column('snapshot', 'is_server_erase', schema=f'{get_inv()}')

View File

@ -676,6 +676,7 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
of time it took to complete. of time it took to complete.
""" """
sid = Column(CIText(), nullable=True) sid = Column(CIText(), nullable=True)
is_server_erase = Column(Boolean(), nullable=True)
def get_last_lifetimes(self): def get_last_lifetimes(self):
"""We get the lifetime and serial_number of the first disk""" """We get the lifetime and serial_number of the first disk"""

View File

@ -296,6 +296,14 @@ class TestDataStorage(Test):
offline_uncorrectable = Integer(data_key='offlineUncorrectable') offline_uncorrectable = Integer(data_key='offlineUncorrectable')
remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage') remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage')
@post_load
def default_remaining_lifetime_percentage(self, data):
if not data.get('remaining_lifetime_percentage'):
return
if data.get('remaining_lifetime_percentage') > 100:
data['remaining_lifetime_percentage'] = None
class StressTest(Test): class StressTest(Test):
__doc__ = m.StressTest.__doc__ __doc__ = m.StressTest.__doc__

View File

@ -115,8 +115,15 @@ class SnapshotMixin:
if snapshot.device.hid is None: if snapshot.device.hid is None:
snapshot.severity = Severity.Warning snapshot.severity = Severity.Warning
self.is_server_erase(snapshot)
return snapshot return snapshot
def is_server_erase(self, snapshot):
if snapshot.device.binding:
if snapshot.device.binding.kangaroo:
snapshot.is_server_erase = True
def get_old_smbios_version(self, debug): def get_old_smbios_version(self, debug):
capabilities = debug.get('lshw', {}).get('capabilities', {}) capabilities = debug.get('lshw', {}).get('capabilities', {})
for x in capabilities.values(): for x in capabilities.values():

View File

@ -10,7 +10,6 @@ from boltons import urlutils
from citext import CIText from citext import CIText
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
from flask import g, request from flask import g, request
from flask_sqlalchemy import event
from more_itertools import unique_everseen from more_itertools import unique_everseen
from sqlalchemy import BigInteger, Boolean, Column from sqlalchemy import BigInteger, Boolean, Column
from sqlalchemy import Enum as DBEnum from sqlalchemy import Enum as DBEnum
@ -75,11 +74,15 @@ def create_code(context):
return hashcode.encode(_id) return hashcode.encode(_id)
def create_phid(context): def create_phid(context, count=1):
_hid = Placeholder.query.order_by(Placeholder.id.desc()).first() phid = str(Placeholder.query.filter(Placeholder.owner == g.user).count() + count)
if _hid: if (
return str(_hid.id + 1) Placeholder.query.filter(Placeholder.owner == g.user)
return '1' .filter(Placeholder.phid == phid)
.count()
):
return create_phid(context, count=count + 1)
return phid
class Device(Thing): class Device(Thing):
@ -650,6 +653,21 @@ class Device(Thing):
args[POLYMORPHIC_ON] = cls.type args[POLYMORPHIC_ON] = cls.type
return args return args
def get_lots_for_template(self):
lots = []
for lot in self.lots:
if lot.is_incoming:
name = "IN - " + lot.name
lots.append(name)
if lot.is_outgoing:
name = "OUT - " + lot.name
lots.append(name)
if lot.is_temporary:
name = "TEMP - " + lot.name
lots.append(name)
lots.sort()
return lots
def phid(self): def phid(self):
if self.placeholder: if self.placeholder:
return self.placeholder.phid return self.placeholder.phid
@ -901,8 +919,8 @@ class DisplayMixin:
class Placeholder(Thing): class Placeholder(Thing):
id = Column(BigInteger, Sequence('placeholder_seq'), primary_key=True) id = Column(BigInteger, Sequence('placeholder_seq'), primary_key=True)
pallet = Column(Unicode(), nullable=True)
phid = Column(Unicode(), nullable=False, default=create_phid) phid = Column(Unicode(), nullable=False, default=create_phid)
pallet = Column(Unicode(), nullable=True)
pallet.comment = "used for identification where from where is this placeholders" pallet.comment = "used for identification where from where is this placeholders"
info = db.Column(CIText()) info = db.Column(CIText())
components = Column(CIText()) components = Column(CIText())
@ -912,6 +930,9 @@ class Placeholder(Thing):
id_device_supplier.comment = ( id_device_supplier.comment = (
"Identification used for one supplier of one placeholders" "Identification used for one supplier of one placeholders"
) )
id_device_internal = db.Column(CIText())
id_device_internal.comment = "Identification used internaly for the user"
kangaroo = db.Column(Boolean, default=False, nullable=True)
device_id = db.Column( device_id = db.Column(
BigInteger, BigInteger,
@ -1593,4 +1614,5 @@ def create_code_tag(mapper, connection, device):
db.session.add(tag) db.session.add(tag)
# from flask_sqlalchemy import event
# event.listen(Device, 'after_insert', create_code_tag, propagate=True) # event.listen(Device, 'after_insert', create_code_tag, propagate=True)

View File

@ -137,7 +137,10 @@ class DeviceView(View):
return self.one_private(id) return self.one_private(id)
def one_public(self, id: int): def one_public(self, id: int):
device = Device.query.filter_by(devicehub_id=id, active=True).one() devices = Device.query.filter_by(devicehub_id=id, active=True).all()
if not devices:
devices = [Device.query.filter_by(dhid_bk=id, active=True).one()]
device = devices[0]
abstract = None abstract = None
if device.binding: if device.binding:
return flask.redirect(device.public_link) return flask.redirect(device.public_link)

View File

@ -26,3 +26,10 @@
.help { .help {
color: #993365; color: #993365;
} }
.doTransfer {
margin-top: -8px;
margin-right: 15px;"
}
.printLabelForm {
margin-left: 10px;
}

View File

@ -29,14 +29,18 @@ function amountInputs() {
if ($("#amount").val() > 1) { if ($("#amount").val() > 1) {
$("#Phid").hide(); $("#Phid").hide();
$("#Id_device_supplier").hide(); $("#Id_device_supplier").hide();
$("#Id_device_internal").hide();
$("#Serial_number").hide(); $("#Serial_number").hide();
$("#Part_number").hide();
$("#Sku").hide(); $("#Sku").hide();
$("#imei").hide(); $("#imei").hide();
$("#meid").hide(); $("#meid").hide();
} else { } else {
$("#Phid").show(); $("#Phid").show();
$("#Id_device_supplier").show(); $("#Id_device_supplier").show();
$("#Id_device_internal").show();
$("#Serial_number").show(); $("#Serial_number").show();
$("#Part_number").show();
$("#Sku").show(); $("#Sku").show();
deviceInputs(); deviceInputs();
}; };

View File

@ -3,11 +3,7 @@
{% block body %} {% block body %}
<!-- ======= Header ======= --> <!-- ======= Header ======= -->
<header id="header" class="header fixed-top d-flex align-items-center"> <header id="header" class="header fixed-top d-flex align-items-center">
<div class="d-flex align-items-center justify-content-between"> <div class="d-flex align-items-center justify-content-between">
<a href="{{ url_for('inventory.devicelist')}}" class="logo d-flex align-items-center">
<img src="{{ url_for('static', filename='img/logo_usody_clock.png') }}" alt="">
</a>
<i class="bi bi-list toggle-sidebar-btn"></i> <i class="bi bi-list toggle-sidebar-btn"></i>
</div><!-- End Logo --> </div><!-- End Logo -->
@ -43,6 +39,11 @@
<i class="bi bi-search"></i> <i class="bi bi-search"></i>
</a> </a>
</li><!-- End Search Icon--> </li><!-- End Search Icon-->
<li class="nav-item dropdown pe-3">
<a class="nav-link nav-profile d-flex align-items-center pe-0" href="{{ url_for('labels.label_list') }}">
<span class="d-none d-md-block ps-2 pb-3 pt-3">Identifiers</span>
</a>
</li>
<li class="nav-item dropdown pe-3"> <li class="nav-item dropdown pe-3">
@ -64,6 +65,11 @@
<span>Lots Spreadsheet</span> <span>Lots Spreadsheet</span>
</a> </a>
</li> </li>
<li>
<a class="dropdown-item d-flex align-items-center" href="{{ url_for('inventory.device_erasure_list') }}">
<i class="bi bi-tag"></i><span>Data Storage Erasures</span>
</a>
</li>
</ul> </ul>
</li> </li>
@ -188,6 +194,11 @@
{% else %} {% else %}
<ul id="incoming-lots-nav" class="nav-content collapse" data-bs-parent="#sidebar-nav"> <ul id="incoming-lots-nav" class="nav-content collapse" data-bs-parent="#sidebar-nav">
{% endif %} {% endif %}
<li>
<a href="{{ url_for('inventory.new_transfer', type_id='incoming') }}">
<i class="bi bi-plus" style="font-size: larger;"></i><span>New Incoming lot</span>
</a>
</li>
{% for lot in lots %} {% for lot in lots %}
{% if lot.is_incoming %} {% if lot.is_incoming %}
<li> <li>
@ -213,6 +224,11 @@
{% else %} {% else %}
<ul id="outgoing-lots-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"> <ul id="outgoing-lots-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav">
{% endif %} {% endif %}
<li>
<a href="{{ url_for('inventory.new_transfer', type_id='outgoing') }}">
<i class="bi bi-plus" style="font-size: larger;"></i><span>New Outgoing lot</span>
</a>
</li>
{% for lot in lots %} {% for lot in lots %}
{% if lot.is_outgoing %} {% if lot.is_outgoing %}
<li> <li>
@ -256,22 +272,6 @@
</ul> </ul>
</li><!-- End Temporal Lots Nav --> </li><!-- End Temporal Lots Nav -->
<li class="nav-heading">Others views</li>
<li class="nav-item">
<a class="nav-link collapsed" href="{{ url_for('inventory.device_erasure_list') }}">
<i class="bi bi-tag"></i><span>Data Storage Erasures</span>
</a>
</li><!-- End Other views -->
<li class="nav-heading">Unique Identifiers (Tags)</li>
<li class="nav-item">
<a class="nav-link collapsed" href="{{ url_for('labels.label_list') }}">
<i class="bi bi-tag"></i><span>UI Management</span>
</a>
</li><!-- End Unique Identifiers -->
</ul> </ul>
</aside><!-- End Sidebar--> </aside><!-- End Sidebar-->

View File

@ -90,19 +90,6 @@
{% endif %} {% endif %}
</div> </div>
<div class="form-group mb-2" id="Phid">
<label for="label" class="form-label">{{ form.phid.label }}</label>
{{ form.phid(class_="form-control") }}
<small class="text-muted form-text">Label that you want link to this device</small>
{% if form.phid.errors %}
<p class="text-danger">
{% for error in form.phid.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="form-group mb-2" id="Id_device_supplier"> <div class="form-group mb-2" id="Id_device_supplier">
<label for="label" class="form-label">{{ form.id_device_supplier.label }}</label> <label for="label" class="form-label">{{ form.id_device_supplier.label }}</label>
{{ form.id_device_supplier(class_="form-control") }} {{ form.id_device_supplier(class_="form-control") }}
@ -116,6 +103,19 @@
{% endif %} {% endif %}
</div> </div>
<div class="form-group mb-2" id="Id_device_internal">
<label for="label" class="form-label">{{ form.id_device_internal.label }}</label>
{{ form.id_device_internal(class_="form-control") }}
<small class="text-muted form-text">Identity of device for the internal</small>
{% if form.id_device_internal.errors %}
<p class="text-danger">
{% for error in form.id_device_internal.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label for="label" class="form-label">{{ form.pallet.label }}</label> <label for="label" class="form-label">{{ form.pallet.label }}</label>
{{ form.pallet(class_="form-control") }} {{ form.pallet(class_="form-control") }}

View File

@ -98,6 +98,11 @@
<div class="col-lg-9 col-md-8">{{ placeholder.phid }}</div> <div class="col-lg-9 col-md-8">{{ placeholder.phid }}</div>
</div> </div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Id device internal</div>
<div class="col-lg-9 col-md-8">{{ placeholder.id_device_internal or '' }}</div>
</div>
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-4 label ">Type</div> <div class="col-lg-3 col-md-4 label ">Type</div>
<div class="col-lg-9 col-md-8">{{ placeholder.device.type }}</div> <div class="col-lg-9 col-md-8">{{ placeholder.device.type }}</div>

View File

@ -31,8 +31,8 @@
<div class="col-xl-12"> <div class="col-xl-12">
{% if lot %}
<div class="card"> <div class="card">
{% if lot %}
<div class="card-body pt-3"> <div class="card-body pt-3">
<!-- Bordered Tabs --> <!-- Bordered Tabs -->
@ -41,25 +41,19 @@
<h3> <h3>
<a href="{{ url_for('inventory.lot_edit', id=lot.id) }}">{{ lot.name }}</a> <a href="{{ url_for('inventory.lot_edit', id=lot.id) }}">{{ lot.name }}</a>
</h3> </h3>
{% if lot.transfer.code and lot.transfer.user_to and not lot.transfer.user_to.phantom %}
<span>{{ lot.transfer.code }} <i class="bi bi-arrow-right"></i> {{ lot.transfer.user_to.email }}</span>
{% elif lot.transfer.code and lot.transfer.user_from and not lot.transfer.user_from.phantom %}
<span>{{ lot.transfer.user_from.email }} <i class="bi bi-arrow-right"></i> {{ lot.transfer.code }}</span>
{% endif %}
</div> </div>
<div class="col-sm-12 col-md-7 d-md-flex justify-content-md-end"><!-- lot actions --> <div class="col-sm-12 col-md-7 d-md-flex justify-content-md-end"><!-- lot actions -->
{% if lot.is_temporary or not lot.transfer.closed %} {% if lot.is_temporary or not lot.transfer.closed %}
{% if 1 == 2 %}{# <!-- TODO (@slamora) Don't render Trade buttons until implementation is finished --> #} {% if lot and lot.is_temporary %}
<a class="me-2" href="javascript:newTrade('user_from')"> <a type="button" href="{{ url_for('inventory.lot_new_transfer', lot_id=lot.id, type_id='outgoing') }}" class="btn btn-primary doTransfer" >
<i class="bi bi-arrow-down-right"></i> Add supplier Create Outgoing Lot
</a> </a>
<a class="me-2" href="javascript:newTrade('user_to')"> <a type="button" href="{{ url_for('inventory.lot_new_transfer', lot_id=lot.id, type_id='incoming') }}" class="btn btn-primary doTransfer">
<i class="bi bi-arrow-up-right"></i> Add receiver Create Incoming Lot
</a> </a>
{% endif %}{# <!-- /end TODO --> #} {% endif %}
<a class="text-danger" href="javascript:removeLot()"> <a class="text-danger" href="javascript:removeLot()">
<i class="bi bi-trash"></i> Delete Lot <i class="bi bi-trash"></i> Delete Lot
</a> </a>
@ -68,18 +62,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
{% endif %} {% endif %}
<div class="card">
<div class="card-body pt-3" style="min-height: 650px;"> <div class="card-body pt-3" style="min-height: 650px;">
<!-- Bordered Tabs --> <!-- Bordered Tabs -->
{% if lot and not lot.is_temporary %} {% if lot %}
<ul class="nav nav-tabs nav-tabs-bordered"> <ul class="nav nav-tabs nav-tabs-bordered">
<li class="nav-item"> <li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#devices-list">Devices</button> <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#devices-list">Devices</button>
</li> </li>
{% if lot and not lot.is_temporary %}
<li class="nav-item"> <li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#trade-documents-list">Documents</button> <button class="nav-link" data-bs-toggle="tab" data-bs-target="#trade-documents-list">Documents</button>
</li> </li>
@ -101,21 +94,11 @@
</button> </button>
</li> </li>
{% endif %} {% endif %}
{% endif %}
</ul> </ul>
{% endif %} {% endif %}
<div class="tab-content pt-1"> <div class="tab-content pt-1">
{% if lot and lot.is_temporary %}
<div class="tab-pane active show mb-5">
<a type="button" href="{{ url_for('inventory.new_transfer', lot_id=lot.id, type_id='outgoing') }}" class="btn btn-primary" style="float: right;">
Outgoing Transfer
</a>
<a type="button" href="{{ url_for('inventory.new_transfer', lot_id=lot.id, type_id='incoming') }}" class="btn btn-primary" style="float: right; margin-right: 15px;">
Incoming Transfer
</a>
<div style="display: block;"></div>
</div>
{% endif %}
<div id="devices-list" class="tab-pane fade devices-list active show"> <div id="devices-list" class="tab-pane fade devices-list active show">
<label class="btn btn-primary " for="SelectAllBTN"><input type="checkbox" id="SelectAllBTN" autocomplete="off"></label> <label class="btn btn-primary " for="SelectAllBTN"><input type="checkbox" id="SelectAllBTN" autocomplete="off"></label>
<div class="btn-group dropdown ml-1"> <div class="btn-group dropdown ml-1">
@ -336,24 +319,6 @@
</ul> </ul>
</div> </div>
{% if lot and not lot.is_temporary %}
<div class="btn-group dropdown ml-1" uib-dropdown="">
<button id="btnSnapshot" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-book"></i>
Documents
</button>
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
<li>
<a href="{{ url_for('inventory.trade_document_add', lot_id=lot.id)}}" class="dropdown-item">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</li>
</ul>
</div>
{% endif %}
<div id="select-devices-info" class="alert alert-info mb-0 mt-3 d-none" role="alert"> <div id="select-devices-info" class="alert alert-info mb-0 mt-3 d-none" role="alert">
If this text is showing is because there are an error If this text is showing is because there are an error
</div> </div>
@ -392,6 +357,7 @@
</thead> </thead>
<tbody> <tbody>
{% for dev in devices %} {% for dev in devices %}
{% if dev.placeholder and (not dev.parent_id or dev.parent.placeholder.kangaroo) %}
<tr> <tr>
<td> <td>
<input type="checkbox" class="deviceSelect" data="{{ dev.id }}" <input type="checkbox" class="deviceSelect" data="{{ dev.id }}"
@ -411,8 +377,8 @@
</a> </a>
{% if dev.lots | length > 0 %} {% if dev.lots | length > 0 %}
<h6 class="d-inline"> <h6 class="d-inline">
{% for lot in dev.lots %} {% for lot in dev.get_lots_for_template() %}
<span class="badge rounded-pill bg-light text-dark">{{ lot.name }}</span> <span class="badge rounded-pill bg-light text-dark">{{ lot }}</span>
{% endfor %} {% endfor %}
</h6> </h6>
{% endif %} {% endif %}
@ -445,6 +411,7 @@
</a> </a>
</td> </td>
</tr> </tr>
{% endif %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -453,12 +420,20 @@
</div> </div>
{% if lot and not lot.is_temporary %} {% if lot and not lot.is_temporary %}
<div id="trade-documents-list" class="tab-pane fade trade-documents-list"> <div id="trade-documents-list" class="tab-pane fade trade-documents-list">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="{{ url_for('inventory.trade_document_add', lot_id=lot.id)}}" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</div>
<h5 class="card-title">Documents</h5> <h5 class="card-title">Documents</h5>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th scope="col">File</th> <th scope="col">File</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Uploaded on</th> <th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -472,7 +447,7 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{{ doc.created.strftime('%H:%M %d-%m-%Y')}} {{ doc.created.strftime('%Y-%m-%d %H:%M')}}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -486,7 +461,7 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{{ doc.created.strftime('%H:%M %d-%m-%Y')}} {{ doc.created.strftime('%Y-%m-%d %H:%M')}}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -38,7 +38,7 @@
<div class="col-12"> <div class="col-12">
{% if field != form.type %} {% if field != form.type %}
{{ field.label(class_="form-label") }} {{ field.label(class_="form-label") }}
{% if field == form.code %} {% if field in [form.code, form.lot_name] %}
<span class="text-danger">*</span> <span class="text-danger">*</span>
{% endif %} {% endif %}
{{ field }} {{ field }}
@ -55,7 +55,7 @@
{% endfor %} {% endfor %}
<div> <div>
<a href="{{ url_for('inventory.lotdevicelist', lot_id=form._tmp_lot.id) }}" class="btn btn-danger">Cancel</a> <a href="{{ referrer }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button> <button class="btn btn-primary" type="submit">Save</button>
</div> </div>
</form> </form>

View File

@ -27,10 +27,10 @@
<tr> <tr>
<th scope="col">PHID</th> <th scope="col">PHID</th>
<th scope="col">Placeholder source</th> <th scope="col">Placeholder source</th>
<th scope="col">Type</th> <th scope="col">Type Upload</th>
<th scope="col">DHID</th> <th scope="col">DHID</th>
<th scope="col">Status</th> <th scope="col">Status</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Time</th> <th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Time</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -53,7 +53,7 @@
<td> <td>
{{ log.get_status() }} {{ log.get_status() }}
</td> </td>
<td>{{ log.created.strftime('%H:%M %d-%m-%Y') }}</td> <td>{{ log.created.strftime('%Y-%m-%d %H:%M') }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -26,7 +26,7 @@
<thead> <thead>
<tr> <tr>
<th scope="col">SID</th> <th scope="col">SID</th>
<th scope="col">Snapshot id</th> <th scope="col">Snapshot UUID</th>
<th scope="col">Version</th> <th scope="col">Version</th>
<th scope="col">DHID</th> <th scope="col">DHID</th>
<th scope="col">System UUID</th> <th scope="col">System UUID</th>
@ -34,7 +34,7 @@
<th scope="col">Type Upload</th> <th scope="col">Type Upload</th>
<th scope="col">Type Device</th> <th scope="col">Type Device</th>
<th scope="col">Original DHID</th> <th scope="col">Original DHID</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Time</th> <th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Time</th>
<th scope="col"></th> <th scope="col"></th>
</tr> </tr>
</thead> </thead>
@ -80,7 +80,7 @@
<td> <td>
{{ snap.original_dhid }} {{ snap.original_dhid }}
</td> </td>
<td>{{ snap.created.strftime('%H:%M %d-%m-%Y') }}</td> <td>{{ snap.created.strftime('%Y-%m-%d %H:%M') }}</td>
<td> <td>
{% if snap.snapshot_uuid %} {% if snap.snapshot_uuid %}
<a href="{{ url_for('inventory.export', export_id='snapshot') }}?id={{ snap.snapshot_uuid }}" target="_blank"> <a href="{{ url_for('inventory.export', export_id='snapshot') }}?id={{ snap.snapshot_uuid }}" target="_blank">

View File

@ -47,7 +47,7 @@
<th scope="col">Type</th> <th scope="col">Type</th>
<th scope="col">Provider</th> <th scope="col">Provider</th>
<th scope="col">Device</th> <th scope="col">Device</th>
<th scope="col">Created</th> <th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -63,7 +63,7 @@
</a> </a>
{% endif %} {% endif %}
</td> </td>
<td>{{ tag.created.strftime('%H:%M %d-%m-%Y') }}</td> <td>{{ tag.created.strftime('%Y-%m-%d %H:%M') }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -13,7 +13,7 @@
<section class="section profile"> <section class="section profile">
<div class="row"> <div class="row">
<div class="col-xl-8"> <div class="col-xxl-9">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -91,8 +91,7 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="col-1"> <div class="col-1 d-none d-md-block printLabelForm"></div>
</div>
<div class="col label"> <div class="col label">
<label class="col-form-label col-sm-2">Logo</label> <label class="col-form-label col-sm-2">Logo</label>
@ -153,7 +152,7 @@
</div> </div>
<div class="form-switch"> <div class="form-switch">
<input class="form-check-input" name="tags" type="checkbox" id="tagsCheck"> <input class="form-check-input" name="tags" type="checkbox" id="tagsCheck">
<label class="form-check-label" for="tagsCheck">Tags</label> <label class="form-check-label" for="tagsCheck">Unique Identifiers</label>
</div> </div>
<div class="form-switch"> <div class="form-switch">
<input class="form-check-input" name="serial_number" type="checkbox" id="serialNumberCheck"> <input class="form-check-input" name="serial_number" type="checkbox" id="serialNumberCheck">

View File

@ -14,6 +14,62 @@
<div class="row"> <div class="row">
<div class="col-xl-6"> <div class="col-xl-6">
<div class="card">
<div class="card-body">
<div class="pt-6 pb-2">
<div class="row pt-3">
<div class="col">
<form method="post">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">PHID</th>
<th scope="col" class="text-center">Erasure Host</th>
</tr>
</thead>
<tbody>
{% for host in form.kangaroos %}
<tr>
<td>{{ host.phid }}</td>
<td class="text-center">
<a href="{{ url_for('workbench.erasure_host', id=host.id) }}"><i class="bi bi-x-lg"></i></a>
</td>
</tr>
{% endfor %}
<tr>
<td>
{% for f in form %}
{{ f }}
{% if f == form.phid and f.errors %}
<p class="text-danger">
{% for error in f.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
{% endfor %}
</td>
<td class="text-center">
<input type="submit" class="btn btn-primary" value="Add new host" />
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -26,120 +82,150 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-xl-8">
</div>
</div> </div>
{% if demo %}
<div class="row"> <div class="row">
<div class="col-xl-6"> <div class="col">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="pt-6 pb-2"> <div class="pt-6 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Workbench 2022</h5> <h5 class="card-title pb-0 fs-4">Usody Hardware Metadata 2022</h5>
<div class="row pt-3"> <p class="mb-5">
<div class="col-5"> A certified collection of hardware details and testing reports<br />
<a href="{{ iso.demo.url }}{{ iso.demo.iso }}" class="btn btn-primary">{{ iso.demo.iso }}</a> <small>Desktops, Servers and Laptops</small>
</p>
<div class="row">
<div class="col-2">
<a href="{{ iso_register.url }}{{ iso_register.iso }}" class="btn btn-primary" style="max-width: 200px;">
Download ISO
</a>
</div> </div>
<div class="col"> <div class="col-3">
<p class="small"> <p class="small">
Download Checksum: <a class="help" href="{{ iso.demo.url }}SHA512SUM">SHA512SUM</a> | Download Checksum: <a class="help" href="{{ iso_register.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a></p> <a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
</p> </p>
</div> </div>
<div class="col"></div>
</div> </div>
<div class="row mt-3">
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Basic Metadata</h5>
<p>Settings for basic hardware metadata collection and hard drive smart Test reports.</p>
<a href="{{ url_for('workbench.settings') }}?opt=register" class="btn btn-primary" style="width: 100%">Download settings file</a>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-xl-8">
</div>
</div> </div>
{% if not demo %}
{% else %}
{% for d, v in iso.items() %}
{% if d != 'demo' %}
<div class="row"> <div class="row">
<div class="col-xl-6"> <div class="col">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="pt-6 pb-2"> <div class="pt-8 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Workbench {{ d }}</h5> <h5 class="card-title pb-0 fs-4">Usody Data Erasure v14</h5>
{% if d == 'v14' %} <p class="mb-5">
A certified data erasure software to irreversibly removing data sored on hard drives.<br />
<small>Desktops, Servers and Laptops</small>
</p>
<div class="row"> <div class="row">
<div class="col-5"> <div class="col-2">
<a href="{{ url_for('workbench.settings') }}?opt=erease_basic" class="btn btn-primary" style="width: 200px;">Get settings file!</a> <a href="{{ iso_erease.url }}{{ iso_erease.iso }}" class="btn btn-primary" style="max-width: 200px;">
Download ISO
</a>
</div> </div>
<div class="col"> <div class="col-3">
<p class="small"> <p class="small">
Settings for basic data erasure. | <a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a> Download Checksum: <a class="help" href="{{ iso_erease.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
</p> </p>
</div> </div>
<div class="col"></div>
</div>
<div class="row mt-3">
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Basic Erasure</h5>
<p>
Settings for basic data erasure using shred Linux command.
A software-based fast non-100%-secured way of erasing data storage.
</p>
<p>
Performs <strong>1</strong> pass overwriting one round using all zeros.
Compliant with <strong>NIST SP-800-88</strong>
</p>
<a href="{{ url_for('workbench.settings') }}?opt=erease_basic"
class="btn btn-primary"
style="width: 100%;">
Download settings file
</a>
</div>
</div>
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Baseline Secure Erasure</h5>
<p>
Settings for advanced data erasure using badblocks Linux software.
A secured-way of erasing data storages, erase hidden areas, checking the erase sector by sector.
</p>
<p>
Performs <strong>1</strong> pass overwriting each sector with zeros and a final verification.
Compliant with <strong>HMG Infosec Standard 5 Baseline</strong>.
</p>
<a href="{{ url_for('workbench.settings') }}?opt=erease_sectors"
class="btn btn-primary"
style="width: 100%;">
Download settings file
</a>
</div>
</div>
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Enhanced Secure Erasure</h5>
<p>
Settings for advanced data erasure using badblocks Linux software.
A secured-way of erasing data storages, erase hidden areas, checking the erase sector by sector.
</p>
<p>
Performs <strong>3</strong> passes overwriting every sector with zeros and ones, and final verification.
Compliant with <strong>HMG Infosec Standard 5 Enhanced</strong>.
</p>
<span
class="btn btn-secondary"
style="width: 100%;">
Download settings file
</span>
</div>
</div>
</div> </div>
<div class="row pt-2">
<div class="col-5">
<a href="{{ url_for('workbench.settings') }}?opt=erease_sectors" class="btn btn-primary" style="width: 200px;">Get settings file!</a>
</div>
<div class="col">
<p class="small">
Settings for advanced data erasure through sectors and hidden areas. Guarantee of data removal. | <a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
</p>
</div>
</div>
{% else %}
<div class="row">
<div class="col-5">
<a href="{{ url_for('workbench.settings') }}?opt=register" class="btn btn-primary" style="width: 200px;">Get settings file!</a>
</div>
<div class="col">
<p class="small">
Settings for register devices.
</p>
</div>
</div>
{% endif %}
{% if iso %}
<br />
<div class="row">
<div class="col-5">
<a href="{{ v.url }}{{ v.iso }}" class="btn btn-primary" style="width: 200px;">Get ISO file</a>
</div>
<div class="col">
<p class="small">
{{ v.iso }}
</p>
<p class="small">
Download Checksum: <a class="help" href="{{ v.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a></p>
</p>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="col-xl-8">
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endfor %}
{% endif %}
</section> </section>
{% endblock main %} {% endblock main %}

View File

@ -3,11 +3,11 @@ isos = {
'iso': "USODY_2022.8.0-Demo.iso", 'iso': "USODY_2022.8.0-Demo.iso",
'url': 'http://releases.usody.com/demo/', 'url': 'http://releases.usody.com/demo/',
}, },
"2022": { "register": {
'iso': "USODY_2022.8.0-beta.iso", 'iso': "USODY_2022.8.0-beta.iso",
'url': 'http://releases.usody.com/2022/', 'url': 'http://releases.usody.com/2022/',
}, },
"v14": { "erease": {
'iso': "USODY_14.0.0.iso", 'iso': "USODY_14.0.0.iso",
'url': 'http://releases.usody.com/v14/', 'url': 'http://releases.usody.com/v14/',
}, },

View File

@ -0,0 +1,47 @@
from flask import g
from flask_wtf import FlaskForm
from wtforms import StringField, validators
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Placeholder
class KangarooForm(FlaskForm):
phid = StringField('Phid', [validators.length(min=1)])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.placeholder = None
self.kangaroos = Placeholder.query.filter(
Placeholder.kangaroo.is_(True)
).filter(Placeholder.owner_id == g.user.id)
def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators)
if not is_valid:
return False
if not self.placeholder:
self.placeholder = (
Placeholder.query.filter(Placeholder.phid == self.phid.data)
.filter(Placeholder.owner_id == g.user.id)
.first()
)
if self.placeholder:
if self.placeholder.status not in ['Snapshot', 'Twin']:
self.placeholder = None
if not self.placeholder:
self.phid.errors = ["Device not exist"]
return False
return True
def save(self):
if not self.placeholder or self.placeholder.kangaroo:
return
self.placeholder.kangaroo = True
db.session.commit()
return self.placeholder

View File

@ -3,34 +3,45 @@ import time
import flask import flask
from flask import Blueprint from flask import Blueprint
from flask import current_app as app from flask import current_app as app
from flask import g, make_response, request from flask import g, make_response, request, url_for
from flask.views import View
from flask_login import login_required from flask_login import login_required
from ereuse_devicehub import auth from ereuse_devicehub import auth
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Placeholder
from ereuse_devicehub.resources.enums import SessionType from ereuse_devicehub.resources.enums import SessionType
from ereuse_devicehub.resources.user.models import Session from ereuse_devicehub.resources.user.models import Session
from ereuse_devicehub.views import GenericMixin from ereuse_devicehub.views import GenericMixin
from ereuse_devicehub.workbench import isos from ereuse_devicehub.workbench import isos
from ereuse_devicehub.workbench.forms import KangarooForm
workbench = Blueprint('workbench', __name__, url_prefix='/workbench') workbench = Blueprint('workbench', __name__, url_prefix='/workbench')
class SettingsView(GenericMixin): class SettingsView(GenericMixin):
decorators = [login_required] decorators = [login_required]
methods = ['GET', 'POST']
template_name = 'workbench/settings.html' template_name = 'workbench/settings.html'
page_title = "Workbench" page_title = "Snapshots"
def dispatch_request(self): def dispatch_request(self):
self.get_context() self.get_context()
form_kangaroo = KangarooForm()
self.context.update( self.context.update(
{ {
'page_title': self.page_title, 'page_title': self.page_title,
'demo': g.user.email == app.config['EMAIL_DEMO'], 'demo': g.user.email == app.config['EMAIL_DEMO'],
'iso': isos, 'iso_demo': isos['demo'],
'iso_register': isos['register'],
'iso_erease': isos['erease'],
'form': form_kangaroo,
} }
) )
if form_kangaroo.validate_on_submit():
form_kangaroo.save()
self.opt = request.values.get('opt') self.opt = request.values.get('opt')
if self.opt in ['register', 'erease_basic', 'erease_sectors']: if self.opt in ['register', 'erease_basic', 'erease_sectors']:
return self.download() return self.download()
@ -86,4 +97,24 @@ class SettingsView(GenericMixin):
return token return token
class ErasureHostView(View):
decorators = [login_required]
methods = ['GET']
def dispatch_request(self, id):
self.placeholder = (
Placeholder.query.filter(Placeholder.id == id)
.filter(Placeholder.kangaroo.is_(True))
.filter(Placeholder.owner_id == g.user.id)
.one()
)
self.placeholder.kangaroo = False
db.session.commit()
return flask.redirect(url_for('workbench.settings'))
workbench.add_url_rule('/', view_func=SettingsView.as_view('settings')) workbench.add_url_rule('/', view_func=SettingsView.as_view('settings'))
workbench.add_url_rule(
'/erasure_host/<int:id>/', view_func=ErasureHostView.as_view('erasure_host')
)

View File

@ -17,7 +17,7 @@ from ereuse_devicehub.workbench.views import workbench
# from flask_wtf.csrf import CSRFProtect # from flask_wtf.csrf import CSRFProtect
# from werkzeug.contrib.profiler import ProfilerMiddleware # from werkzeug.middleware.profiler import ProfilerMiddleware
SENTRY_DSN = config('SENTRY_DSN', None) SENTRY_DSN = config('SENTRY_DSN', None)

View File

@ -1,9 +1,9 @@
from ereuse_devicehub.devicehub import Devicehub from werkzeug.middleware.profiler import ProfilerMiddleware
from werkzeug.contrib.profiler import ProfilerMiddleware
from ereuse_devicehub.devicehub import Devicehub
app = Devicehub(inventory='dbtest') app = Devicehub(inventory='dbtest')
app.config["SQLALCHEMY_RECORD_QUERIES"] = True app.config["SQLALCHEMY_RECORD_QUERIES"] = True
app.config['PROFILE'] = True app.config['PROFILE'] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30]) app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(debug = True) app.run(debug=True)

View File

@ -21,7 +21,7 @@
"scripts": { "scripts": {
"lint:report": "eslint ereuse_devicehub --ext .js --output-file eslint_report.json --format json", "lint:report": "eslint ereuse_devicehub --ext .js --output-file eslint_report.json --format json",
"lint:fix": "eslint ereuse_devicehub --ext .js --fix", "lint:fix": "eslint ereuse_devicehub --ext .js --fix",
"babel": "babel ereuse_devicehub/static/js/main_inventory.js --out-file ereuse_devicehub/static/js/main_inventory.build.js" "babel": "babel ereuse_devicehub/static/js/main_inventory.js --out-file ereuse_devicehub/static/js/main_inventory.build.js && echo 'Ignoring parameters: '"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

41
requirements.in Normal file
View File

@ -0,0 +1,41 @@
alembic==1.4.2
atomicwrites==1.4.0
click-spinner==0.1.8
colorama==0.3.9
colour==0.1.5
ereuse-utils[naming,test,session,cli]==0.4.0b50
Flask-Cors==3.0.10
Flask-Login==0.5.0
Flask-WTF==1.0.0
flask-weasyprint==0.4
hashids==1.2.0
more-itertools==8.12.0
passlib==1.7.1
phonenumbers==8.9.11
psycopg2-binary==2.8.3
pyjwt==2.4.0
python-decouple==3.3
python-dotenv==0.14.0
python-stdnum==1.9
pyyaml==5.4
requests==2.27.1
requests-mock==1.5.2
requests-toolbelt==0.9.1
sortedcontainers==2.1.0
sqlalchemy-citext==1.3.post0
sqlalchemy-utils==0.33.11
teal==0.2.0a38
tqdm==4.32.2
# workbench json parsing dependencies
pint==0.9
py-dmidecode==0.1.0
pandas==1.3.5
numpy==1.22.0 # pandas dependency
odfpy==1.4.1 # pandas dependency
xlrd==2.0.1 # pandas dependency
openpyxl==3.0.10 # pandas dependency
et_xmlfile==1.1.0 # pandas dependency
# manual dependency
marshmallow-enum==1.4.1

View File

@ -1,53 +1,226 @@
#
# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
# pip-compile --output-file=requirements.txt requirements.in
#
alembic==1.4.2 alembic==1.4.2
# via -r requirements.in
anytree==2.4.3 anytree==2.4.3
# via teal
apispec==0.39.0 apispec==0.39.0
# via teal
atomicwrites==1.4.0
# via -r requirements.in
boltons==18.0.1 boltons==18.0.1
# via
# ereuse-utils
# teal
cairocffi==1.4.0
# via
# cairosvg
# weasyprint
cairosvg==2.5.2
# via weasyprint
certifi==2022.9.24
# via requests
cffi==1.15.1
# via
# cairocffi
# weasyprint
charset-normalizer==2.0.12
# via requests
click==6.7 click==6.7
# via
# ereuse-utils
# flask
click-spinner==0.1.8 click-spinner==0.1.8
# via
# -r requirements.in
# teal
colorama==0.3.9 colorama==0.3.9
# via
# -r requirements.in
# ereuse-utils
colour==0.1.5 colour==0.1.5
ereuse-utils[naming,test,session,cli]==0.4.0b50 # via
Flask==1.0.2 # -r requirements.in
Flask-Cors==3.0.10 # sqlalchemy-utils
Flask-Login==0.5.0 cssselect2==0.7.0
Flask-SQLAlchemy==2.3.2 # via
Flask-WTF==1.0.0 # cairosvg
# weasyprint
defusedxml==0.7.1
# via
# cairosvg
# odfpy
ereuse-utils[cli,naming,session,test]==0.4.0b50
# via
# -r requirements.in
# teal
et-xmlfile==1.1.0
# via
# -r requirements.in
# openpyxl
flask==1.0.2
# via
# ereuse-utils
# flask-cors
# flask-login
# flask-sqlalchemy
# flask-weasyprint
# flask-wtf
# teal
flask-cors==3.0.10
# via
# -r requirements.in
# teal
flask-login==0.5.0
# via -r requirements.in
flask-sqlalchemy==2.5.1
# via teal
flask-weasyprint==0.4
# via -r requirements.in
flask-wtf==1.0.0
# via -r requirements.in
hashids==1.2.0 hashids==1.2.0
# via -r requirements.in
html5lib==1.1
# via weasyprint
idna==3.4
# via requests
inflection==0.3.1 inflection==0.3.1
# via ereuse-utils
itsdangerous==2.0.1 itsdangerous==2.0.1
# lock Jinja2 version because it's the latest compatible with Flask 1.0.X # via
# see related info on https://github.com/pallets/jinja/issues/1628 # flask
Jinja2==3.0.3 # flask-wtf
jinja2==3.0.3
# via flask
mako==1.2.3
# via alembic
markupsafe==2.1.1
# via
# jinja2
# mako
# wtforms
marshmallow==3.0.0b11 marshmallow==3.0.0b11
# via
# marshmallow-enum
# teal
# webargs
marshmallow-enum==1.4.1 marshmallow-enum==1.4.1
passlib==1.7.1 # via -r requirements.in
phonenumbers==8.9.11 more-itertools==8.12.0
pytest==3.7.2 # via -r requirements.in
pytest-runner==4.2 numpy==1.22.0
python-dateutil==2.7.3 # via
python-stdnum==1.9 # -r requirements.in
PyYAML==5.4 # pandas
requests[security]==2.27.1 odfpy==1.4.1
requests-mock==1.5.2 # via -r requirements.in
SQLAlchemy==1.3.24 openpyxl==3.0.10
SQLAlchemy-Utils==0.33.11 # via -r requirements.in
teal==0.2.0a38
webargs==5.5.3
Werkzeug==0.15.5
sqlalchemy-citext==1.3.post0
flask-weasyprint==0.5
weasyprint==44
psycopg2-binary==2.8.3
sortedcontainers==2.1.0
tqdm==4.32.2
python-decouple==3.3
python-dotenv==0.14.0
pyjwt==2.4.0
pint==0.9
py-dmidecode==0.1.0
pandas==1.3.5 pandas==1.3.5
numpy==1.22.0 # pandas dependency # via -r requirements.in
odfpy==1.4.1 # pandas dependency passlib==1.7.1
xlrd==2.0.1 # pandas dependency # via
openpyxl==3.0.10 # pandas dependency # -r requirements.in
et_xmlfile==1.1.0 # pandas dependency # sqlalchemy-utils
phonenumbers==8.9.11
# via
# -r requirements.in
# sqlalchemy-utils
pillow==9.2.0
# via cairosvg
pint==0.9
# via -r requirements.in
psycopg2-binary==2.8.3
# via -r requirements.in
py-dmidecode==0.1.0
# via -r requirements.in
pycparser==2.21
# via cffi
pyjwt==2.4.0
# via -r requirements.in
pyphen==0.13.0
# via weasyprint
python-dateutil==2.7.3
# via
# alembic
# pandas
python-decouple==3.3
# via -r requirements.in
python-dotenv==0.14.0
# via -r requirements.in
python-editor==1.0.4
# via alembic
python-stdnum==1.9
# via -r requirements.in
pytz==2022.2.1
# via pandas
pyyaml==5.4
# via
# -r requirements.in
# apispec
requests==2.27.1
# via
# -r requirements.in
# requests-mock
# requests-toolbelt
requests-mock==1.5.2
# via -r requirements.in
requests-toolbelt==0.9.1
# via
# -r requirements.in
# ereuse-utils
six==1.16.0
# via
# anytree
# flask-cors
# html5lib
# python-dateutil
# requests-mock
# sqlalchemy-utils
sortedcontainers==2.1.0
# via -r requirements.in
sqlalchemy==1.3.24
# via
# alembic
# flask-sqlalchemy
# sqlalchemy-citext
# sqlalchemy-utils
sqlalchemy-citext==1.3.post0
# via -r requirements.in
sqlalchemy-utils[color,password,phone]==0.33.11
# via
# -r requirements.in
# teal
teal==0.2.0a38
# via -r requirements.in
tinycss2==1.1.1
# via
# cairosvg
# cssselect2
# weasyprint
tqdm==4.32.2
# via
# -r requirements.in
# ereuse-utils
urllib3==1.26.12
# via requests
weasyprint==44
# via flask-weasyprint
webargs==5.5.3
# via teal
webencodings==0.5.1
# via
# cssselect2
# html5lib
# tinycss2
werkzeug==2.0.3
# via flask
wtforms==3.0.1
# via flask-wtf
xlrd==2.0.1
# via -r requirements.in

View File

@ -1,4 +1,4 @@
Phid;Model;Manufacturer;Serial Number;Part Number;Id device Supplier;Pallet;Info Model;Manufacturer;Serial Number;Part Number;Id device Supplier;Id device Internal;Pallet;Info
a123;Vaio;Sony;12345678;;TTT;24A;Good conditions Vaio;Sony;12345678;;TTT;AA;24A;Good conditions
a124;Vaio;Sony;12345679;;TTT;24A;Good conditions Vaio;Sony;12345679;;TTT;BB;24A;Good conditions
a125;Vaio;Sony;12345680;;TTT;24A;Good conditions Vaio;Sony;12345680;;TTT;CC;24A;Good conditions

1 Phid Model Manufacturer Serial Number Part Number Id device Supplier Id device Internal Pallet Info
2 a123 Vaio Sony 12345678 TTT AA 24A Good conditions
3 a124 Vaio Sony 12345679 TTT BB 24A Good conditions
4 a125 Vaio Sony 12345680 TTT CC 24A Good conditions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,4 @@
import copy import copy
import ipaddress
import json import json
import os import os
import shutil import shutil
@ -7,7 +6,7 @@ from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
from io import BytesIO from io import BytesIO
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from typing import Tuple, Type from typing import Tuple
import pytest import pytest
from dateutil.tz import tzutc from dateutil.tz import tzutc
@ -15,7 +14,7 @@ from flask import current_app as app
from flask import g from flask import g
from pytest import raises from pytest import raises
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from teal.enums import Currency, Subdivision from teal.enums import Currency
from ereuse_devicehub.client import Client, UserClient from ereuse_devicehub.client import Client, UserClient
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
@ -82,11 +81,7 @@ def test_erase_basic():
def test_validate_device_data_storage(): def test_validate_device_data_storage():
"""Checks the validation for data-storage-only actions works.""" """Checks the validation for data-storage-only actions works."""
# We can't set a GraphicCard # We can't set a GraphicCard
with pytest.raises( with pytest.raises(TypeError):
TypeError,
message='EraseBasic.device must be a DataStorage '
'but you passed <GraphicCard None model=\'foo-bar\' S/N=\'foo\'>',
):
models.EraseBasic( models.EraseBasic(
device=GraphicCard( device=GraphicCard(
serial_number='foo', manufacturer='bar', model='foo-bar' serial_number='foo', manufacturer='bar', model='foo-bar'
@ -94,6 +89,10 @@ def test_validate_device_data_storage():
clean_with_zeros=True, clean_with_zeros=True,
**conftest.T, **conftest.T,
) )
pytest.fail(
'EraseBasic.device must be a DataStorage '
'but you passed <GraphicCard None model=\'foo-bar\' S/N=\'foo\'>'
)
@pytest.mark.mvp @pytest.mark.mvp
@ -292,9 +291,7 @@ def test_generic_action(
for ams in [models.Recycling, models.Use, models.Refurbish, models.Management] for ams in [models.Recycling, models.Use, models.Refurbish, models.Management]
), ),
) )
def test_simple_status_actions( def test_simple_status_actions(action_model: models.Action, user2: UserClient):
action_model: models.Action, user2: UserClient
):
"""Simple test of status action.""" """Simple test of status action."""
user = user2 user = user2
snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
@ -554,7 +551,9 @@ def test_status_without_lot(action_model: models.Action, user: UserClient):
for ams in [models.Recycling, models.Use, models.Refurbish, models.Management] for ams in [models.Recycling, models.Use, models.Refurbish, models.Management]
), ),
) )
def test_status_in_temporary_lot(action_model: models.Action, user: UserClient, app: Devicehub): def test_status_in_temporary_lot(
action_model: models.Action, user: UserClient, app: Devicehub
):
"""Test of status actions for devices in a temporary lot.""" """Test of status actions for devices in a temporary lot."""
snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
abstract = Device.query.filter_by(id=snap['device']['id']).first() abstract = Device.query.filter_by(id=snap['device']['id']).first()
@ -727,7 +726,7 @@ def test_live_without_TestDataStorage(user: UserClient, client: Client, app: Dev
acer = file('acer.happy.battery.snapshot') acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=models.Snapshot) snapshot, _ = user.post(acer, res=models.Snapshot)
device_id = snapshot['device']['id'] device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = { post_request = {
"transaction": "ccc", "transaction": "ccc",
"name": "John", "name": "John",
@ -767,7 +766,7 @@ def test_live_without_hdd_1(user: UserClient, client: Client, app: Devicehub):
acer = file('acer.happy.battery.snapshot') acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=models.Snapshot) snapshot, _ = user.post(acer, res=models.Snapshot)
device_id = snapshot['device']['id'] device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = { post_request = {
"transaction": "ccc", "transaction": "ccc",
"name": "John", "name": "John",
@ -803,7 +802,7 @@ def test_live_without_hdd_2(user: UserClient, client: Client, app: Devicehub):
acer['components'] = components acer['components'] = components
snapshot, _ = user.post(json_encode(acer), res=models.Snapshot) snapshot, _ = user.post(json_encode(acer), res=models.Snapshot)
device_id = snapshot['device']['id'] device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = { post_request = {
"transaction": "ccc", "transaction": "ccc",
"name": "John", "name": "John",
@ -838,7 +837,7 @@ def test_live_without_hdd_3(user: UserClient, client: Client, app: Devicehub):
acer['components'] = components acer['components'] = components
snapshot, _ = user.post(json_encode(acer), res=models.Snapshot) snapshot, _ = user.post(json_encode(acer), res=models.Snapshot)
device_id = snapshot['device']['id'] device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = { post_request = {
"transaction": "ccc", "transaction": "ccc",
"name": "John", "name": "John",
@ -875,7 +874,7 @@ def test_live_with_hdd_with_old_time(user: UserClient, client: Client, app: Devi
acer = file('acer.happy.battery.snapshot') acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=models.Snapshot) snapshot, _ = user.post(acer, res=models.Snapshot)
device_id = snapshot['device']['id'] device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = { post_request = {
"transaction": "ccc", "transaction": "ccc",
"name": "John", "name": "John",
@ -1022,7 +1021,7 @@ def test_allocate(user: UserClient):
allocate, _ = user.post(res=models.Allocate, data=post_request) allocate, _ = user.post(res=models.Allocate, data=post_request)
# Normal allocate # Normal allocate
device, _ = user.get(res=Device, item=devicehub_id) device, _ = user.get(res=Device, item=devicehub_id)
assert device['allocated'] == True assert device['allocated'] is True
action = [a for a in device['actions'] if a['type'] == 'Allocate'][0] action = [a for a in device['actions'] if a['type'] == 'Allocate'][0]
assert action['transaction'] == allocate['transaction'] assert action['transaction'] == allocate['transaction']
assert action['finalUserCode'] == allocate['finalUserCode'] assert action['finalUserCode'] == allocate['finalUserCode']
@ -1097,11 +1096,11 @@ def test_deallocate(user: UserClient):
user.post(res=models.Allocate, data=post_allocate) user.post(res=models.Allocate, data=post_allocate)
device, _ = user.get(res=Device, item=devicehub_id) device, _ = user.get(res=Device, item=devicehub_id)
assert device['allocated'] == True assert device['allocated'] is True
deallocate, _ = user.post(res=models.Deallocate, data=post_deallocate) deallocate, _ = user.post(res=models.Deallocate, data=post_deallocate)
assert deallocate['startTime'] == post_deallocate['startTime'] assert deallocate['startTime'] == post_deallocate['startTime']
assert deallocate['devices'][0]['id'] == device_id assert deallocate['devices'][0]['id'] == device_id
assert deallocate['devices'][0]['allocated'] == False assert deallocate['devices'][0]['allocated'] is False
res, _ = user.post(res=models.Deallocate, data=post_deallocate, status=422) res, _ = user.post(res=models.Deallocate, data=post_deallocate, status=422)
assert res['code'] == 422 assert res['code'] == 422
assert res['type'] == 'ValidationError' assert res['type'] == 'ValidationError'
@ -1204,8 +1203,8 @@ def test_offer_without_to(user: UserClient):
users = [ac.user for ac in trade.acceptances] users = [ac.user for ac in trade.acceptances]
assert trade.user_to == device.owner assert trade.user_to == device.owner
assert request_post['code'].lower() in device.owner.email assert request_post['code'].lower() in device.owner.email
assert device.owner.active == False assert device.owner.active is False
assert device.owner.phantom == True assert device.owner.phantom is True
assert trade.user_to in users assert trade.user_to in users
assert trade.user_from in users assert trade.user_from in users
assert device.owner.email != user.email assert device.owner.email != user.email
@ -1283,8 +1282,8 @@ def test_offer_without_from(user: UserClient, user2: UserClient):
phantom_user = trade.user_from phantom_user = trade.user_from
assert request_post['code'].lower() in phantom_user.email assert request_post['code'].lower() in phantom_user.email
assert phantom_user.active == False assert phantom_user.active is False
assert phantom_user.phantom == True assert phantom_user.phantom is True
# assert trade.confirm_transfer # assert trade.confirm_transfer
users = [ac.user for ac in trade.acceptances] users = [ac.user for ac in trade.acceptances]
@ -1778,12 +1777,12 @@ def test_confirmRevoke(user: UserClient, user2: UserClient):
assert device_10 in trade.devices assert device_10 in trade.devices
assert len(trade.devices) == 10 assert len(trade.devices) == 10
# the SCRAP confirms the revoke action # TODO??? the SCRAP confirms the revoke action
request_confirm_revoke = { # request_confirm_revoke = {
'type': 'ConfirmRevoke', # 'type': 'ConfirmRevoke',
'action': device_10.actions[-2].id, # 'action': device_10.actions[-2].id,
'devices': [snap10['device']['id']], # 'devices': [snap10['device']['id']],
} # }
# check validation error # check validation error
# user2.post(res=models.Action, data=request_confirm_revoke, status=422) # user2.post(res=models.Action, data=request_confirm_revoke, status=422)
@ -2796,7 +2795,7 @@ def test_action_web_erase(user: UserClient, client: Client):
) )
assert "alert alert-info" in response assert "alert alert-info" in response
assert "100% coincidence." in response assert "100% coincidence." in response
assert not "alert alert-danger" in response assert "alert alert-danger" not in response
@pytest.mark.mvp @pytest.mark.mvp
@ -2805,7 +2804,7 @@ def test_moveOnDocument(user: UserClient, user2: UserClient):
lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot) lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot)
lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot) lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot)
url = ( url = (
'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa', 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaap',
) )
request_post1 = { request_post1 = {
'filename': 'test.pdf', 'filename': 'test.pdf',
@ -2887,7 +2886,7 @@ def test_delete_devices(user: UserClient):
assert action_delete.t == 'Delete' assert action_delete.t == 'Delete'
assert str(action_delete.id) == action['id'] assert str(action_delete.id) == action['id']
assert db_device.active == False assert db_device.active is False
# Check use of filter from frontend # Check use of filter from frontend
url = '/devices/?filter={"type":["Computer"]}' url = '/devices/?filter={"type":["Computer"]}'
@ -2953,7 +2952,6 @@ def test_delete_devices_permitions(user: UserClient, user2: UserClient):
file_snap = file('1-device-with-components.snapshot') file_snap = file('1-device-with-components.snapshot')
snap, _ = user.post(file_snap, res=models.Snapshot) snap, _ = user.post(file_snap, res=models.Snapshot)
device = Device.query.filter_by(id=snap['device']['id']).one()
request = { request = {
'type': 'Delete', 'type': 'Delete',
@ -2973,7 +2971,7 @@ def test_moveOnDocument_bug168(user: UserClient, user2: UserClient):
lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot) lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot)
lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot) lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot)
url = ( url = (
'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa', 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaap',
) )
request_post1 = { request_post1 = {
'filename': 'test.pdf', 'filename': 'test.pdf',
@ -3014,13 +3012,12 @@ def test_moveOnDocument_bug168(user: UserClient, user2: UserClient):
'container_to': id_hash, 'container_to': id_hash,
'description': description, 'description': description,
} }
doc, _ = user.post(res=models.Action, data=request_moveOn) user.post(res=models.Action, data=request_moveOn)
trade = models.Trade.query.one()
trade_document1 = TradeDocument.query.filter_by(id=tradedocument_from['id']).one() trade_document1 = TradeDocument.query.filter_by(id=tradedocument_from['id']).one()
trade_document2 = TradeDocument.query.filter_by(id=tradedocument_to['id']).one() trade_document2 = TradeDocument.query.filter_by(id=tradedocument_to['id']).one()
assert trade_document1.total_weight == 150.0 assert trade_document1.total_weight == 150.0
assert trade_document2.total_weight == 4.0 assert trade_document2.total_weight == 4.0
assert trade_document1.trading == 'Confirm' assert trade_document1.trading == 'Confirm'
assert trade_document2.trading == None assert trade_document2.trading is None
tradedocument, _ = user.delete(res=TradeDocument, item=tradedocument_to['id']) tradedocument, _ = user.delete(res=TradeDocument, item=tradedocument_to['id'])

View File

@ -21,14 +21,22 @@ def test_authenticate_success(app: Devicehub):
def test_authenticate_error(app: Devicehub): def test_authenticate_error(app: Devicehub):
"""Tests the authenticate method with wrong token values.""" """Tests the authenticate method with wrong token values."""
with app.app_context(): with app.app_context():
MESSAGE = 'Provide a suitable token.'
create_user() create_user()
# Token doesn't exist # Token doesn't exist
with pytest.raises(Unauthorized, message=MESSAGE): with pytest.raises(Unauthorized):
app.auth.authenticate(token=str(uuid4())) app.auth.authenticate(token=str(uuid4()))
pytest.fail('Provide a suitable token.')
@pytest.mark.mvp
def test_authenticate_error_malformed_token(app: Devicehub):
"""Tests the authenticate method with malformed token."""
with app.app_context():
create_user()
# Wrong token format # Wrong token format
with pytest.raises(Unauthorized, message=MESSAGE): with pytest.raises(Unauthorized):
app.auth.authenticate(token='this is a wrong uuid') app.auth.authenticate(token='this is a wrong uuid')
pytest.fail('Provide a suitable token.')
@pytest.mark.mvp @pytest.mark.mvp
@ -36,4 +44,6 @@ def test_auth_view(user: UserClient, client: Client):
"""Tests authentication at endpoint / view.""" """Tests authentication at endpoint / view."""
user.get(res='User', item=user.user['id'], status=200) user.get(res='User', item=user.user['id'], status=200)
client.get(res='User', item=user.user['id'], status=Unauthorized) client.get(res='User', item=user.user['id'], status=Unauthorized)
client.get(res='User', item=user.user['id'], token='wrong token', status=Unauthorized) client.get(
res='User', item=user.user['id'], token='wrong token', status=Unauthorized
)

View File

@ -68,6 +68,7 @@ def test_api_docs(client: Client):
'/inventory/lot/{lot_id}/trade-document/add/', '/inventory/lot/{lot_id}/trade-document/add/',
'/inventory/lot/{lot_id}/transfer/{type_id}/', '/inventory/lot/{lot_id}/transfer/{type_id}/',
'/inventory/lot/{lot_id}/transfer/', '/inventory/lot/{lot_id}/transfer/',
'/inventory/lot/transfer/{type_id}/',
'/inventory/lot/{lot_id}/upload-snapshot/', '/inventory/lot/{lot_id}/upload-snapshot/',
'/inventory/snapshots/{snapshot_uuid}/', '/inventory/snapshots/{snapshot_uuid}/',
'/inventory/snapshots/', '/inventory/snapshots/',
@ -104,6 +105,7 @@ def test_api_docs(client: Client):
'/users/logout/', '/users/logout/',
'/versions/', '/versions/',
'/workbench/', '/workbench/',
'/workbench/erasure_host/{id}/',
} }
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'} assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
assert docs['components']['securitySchemes']['bearerAuth'] == { assert docs['components']['securitySchemes']['bearerAuth'] == {

View File

@ -1,13 +1,12 @@
import copy import copy
import datetime import datetime
import pytest
from uuid import UUID from uuid import UUID
from flask import g
import pytest
from colour import Color from colour import Color
from ereuse_utils.naming import Naming from ereuse_utils.naming import Naming
from ereuse_utils.test import ANY from ereuse_utils.test import ANY
from flask import g
from pytest import raises from pytest import raises
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from teal.db import ResourceNotFound from teal.db import ResourceNotFound
@ -20,26 +19,35 @@ from ereuse_devicehub.resources.action import models as m
from ereuse_devicehub.resources.action.models import Remove, TestConnectivity from ereuse_devicehub.resources.action.models import Remove, TestConnectivity
from ereuse_devicehub.resources.agent.models import Person from ereuse_devicehub.resources.agent.models import Person
from ereuse_devicehub.resources.device import models as d from ereuse_devicehub.resources.device import models as d
from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.schemas import Device as DeviceS from ereuse_devicehub.resources.device.schemas import Device as DeviceS
from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, MismatchBetweenTagsAndHid, \ from ereuse_devicehub.resources.device.sync import (
Sync MismatchBetweenTags,
from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech, Severity, \ MismatchBetweenTagsAndHid,
SnapshotSoftware, TransferState Sync,
)
from ereuse_devicehub.resources.enums import (
ComputerChassis,
DisplayTech,
Severity,
SnapshotSoftware,
TransferState,
)
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.user import User from ereuse_devicehub.resources.user import User
from tests import conftest from tests import conftest
from tests.conftest import file, yaml2json, json_encode from tests.conftest import file, json_encode, yaml2json
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_device_model(): def test_device_model():
"""Tests that the correctness of the device model and its relationships.""" """Tests that the correctness of the device model and its relationships."""
pc = d.Desktop(model='p1mo', pc = d.Desktop(
manufacturer='p1ma', model='p1mo',
serial_number='p1s', manufacturer='p1ma',
chassis=ComputerChassis.Tower) serial_number='p1s',
chassis=ComputerChassis.Tower,
)
net = d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s') net = d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s')
graphic = d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500) graphic = d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
pc.components.add(net) pc.components.add(net)
@ -55,7 +63,9 @@ def test_device_model():
# Removing a component from pc doesn't delete the component # Removing a component from pc doesn't delete the component
pc.components.remove(net) pc.components.remove(net)
db.session.commit() db.session.commit()
pc = d.Device.query.filter_by(id=pc.id).first() # this is the same as querying for d.Desktop directly pc = d.Device.query.filter_by(
id=pc.id
).first() # this is the same as querying for d.Desktop directly
assert pc.components == {graphic} assert pc.components == {graphic}
network_adapter = d.NetworkAdapter.query.one() network_adapter = d.NetworkAdapter.query.one()
assert network_adapter not in pc.components assert network_adapter not in pc.components
@ -72,7 +82,9 @@ def test_device_model():
assert network_adapter.id == 4 assert network_adapter.id == 4
assert d.NetworkAdapter.query.first() is not None, 'We removed the network adaptor' assert d.NetworkAdapter.query.first() is not None, 'We removed the network adaptor'
assert gcard.id == 5, 'We should still hold a reference to a zombie graphic card' assert gcard.id == 5, 'We should still hold a reference to a zombie graphic card'
assert d.GraphicCard.query.first() is None, 'We should have deleted it it was inside the pc' assert (
d.GraphicCard.query.first() is None
), 'We should have deleted it it was inside the pc'
@pytest.mark.xfail(reason='Test not developed') @pytest.mark.xfail(reason='Test not developed')
@ -92,21 +104,25 @@ def test_device_schema():
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_physical_properties(): def test_physical_properties():
c = d.Motherboard(slots=2, c = d.Motherboard(
usb=3, slots=2,
serial_number='sn', usb=3,
model='ml', serial_number='sn',
manufacturer='mr', model='ml',
width=2.0, manufacturer='mr',
color=Color()) width=2.0,
pc = d.Desktop(chassis=ComputerChassis.Tower, color=Color(),
model='foo', )
manufacturer='bar', pc = d.Desktop(
serial_number='foo-bar', chassis=ComputerChassis.Tower,
weight=2.8, model='foo',
width=1.4, manufacturer='bar',
height=2.1, serial_number='foo-bar',
color=Color('LightSeaGreen')) weight=2.8,
width=1.4,
height=2.1,
color=Color('LightSeaGreen'),
)
pc.components.add(c) pc.components.add(c)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -122,7 +138,7 @@ def test_physical_properties():
'manufacturer': 'mr', 'manufacturer': 'mr',
'bios_date': None, 'bios_date': None,
'ram_max_size': None, 'ram_max_size': None,
'ram_slots': None 'ram_slots': None,
} }
assert pc.physical_properties == { assert pc.physical_properties == {
'chassis': ComputerChassis.Tower, 'chassis': ComputerChassis.Tower,
@ -132,7 +148,7 @@ def test_physical_properties():
'receiver_id': None, 'receiver_id': None,
'serial_number': 'foo-bar', 'serial_number': 'foo-bar',
'part_number': None, 'part_number': None,
'transfer_state': TransferState.Initial 'transfer_state': TransferState.Initial,
} }
@ -142,14 +158,19 @@ def test_component_similar_one():
user = User.query.filter().first() user = User.query.filter().first()
snapshot = yaml2json('pc-components.db') snapshot = yaml2json('pc-components.db')
pc = snapshot['device'] pc = snapshot['device']
snapshot['components'][0]['serial_number'] = snapshot['components'][1]['serial_number'] = None snapshot['components'][0]['serial_number'] = snapshot['components'][1][
pc = d.Desktop(**pc, components=OrderedSet(d.Component(**c) for c in snapshot['components'])) 'serial_number'
] = None
pc = d.Desktop(
**pc, components=OrderedSet(d.Component(**c) for c in snapshot['components'])
)
component1, component2 = pc.components # type: d.Component component1, component2 = pc.components # type: d.Component
db.session.add(pc) db.session.add(pc)
db.session.flush() db.session.flush()
# Let's create a new component named 'A' similar to 1 # Let's create a new component named 'A' similar to 1
componentA = d.Component(model=component1.model, manufacturer=component1.manufacturer, componentA = d.Component(
owner_id=user.id) model=component1.model, manufacturer=component1.manufacturer, owner_id=user.id
)
similar_to_a = componentA.similar_one(pc, set()) similar_to_a = componentA.similar_one(pc, set())
assert similar_to_a == component1 assert similar_to_a == component1
# d.Component B does not have the same model # d.Component B does not have the same model
@ -175,9 +196,11 @@ def test_add_remove():
pc = d.Desktop(**pc, components=OrderedSet([c1, c2])) pc = d.Desktop(**pc, components=OrderedSet([c1, c2]))
db.session.add(pc) db.session.add(pc)
c3 = d.Component(serial_number='nc1', owner_id=user.id) c3 = d.Component(serial_number='nc1', owner_id=user.id)
pc2 = d.Desktop(serial_number='s2', pc2 = d.Desktop(
components=OrderedSet([c3]), serial_number='s2',
chassis=ComputerChassis.Microtower) components=OrderedSet([c3]),
chassis=ComputerChassis.Microtower,
)
c4 = d.Component(serial_number='c4s', owner_id=user.id) c4 = d.Component(serial_number='c4s', owner_id=user.id)
db.session.add(pc2) db.session.add(pc2)
db.session.add(c4) db.session.add(c4)
@ -201,7 +224,9 @@ def test_sync_run_components_empty():
remove all the components from the device. remove all the components from the device.
""" """
s = yaml2json('pc-components.db') s = yaml2json('pc-components.db')
pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])) pc = d.Desktop(
**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])
)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -219,7 +244,9 @@ def test_sync_run_components_none():
keep all the components from the device. keep all the components from the device.
""" """
s = yaml2json('pc-components.db') s = yaml2json('pc-components.db')
pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])) pc = d.Desktop(
**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])
)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -249,7 +276,8 @@ def test_sync_execute_register_desktop_existing_no_tag():
db.session.commit() db.session.commit()
pc = d.Desktop( pc = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object **yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
# 1: device exists on DB # 1: device exists on DB
db_pc = Sync().execute_register(pc) db_pc = Sync().execute_register(pc)
pc.amount = 0 pc.amount = 0
@ -285,7 +313,9 @@ def test_sync_execute_register_desktop_tag_not_linked():
db.session.commit() db.session.commit()
# Create a new transient non-db object # Create a new transient non-db object
pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')])) pc = d.Desktop(
**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')])
)
returned_pc = Sync().execute_register(pc) returned_pc = Sync().execute_register(pc)
assert returned_pc == pc assert returned_pc == pc
assert tag.device == pc, 'Tag has to be linked' assert tag.device == pc, 'Tag has to be linked'
@ -326,7 +356,9 @@ def test_sync_execute_register_tag_does_not_exist():
Tags have to be created before trying to link them through a Snapshot. Tags have to be created before trying to link them through a Snapshot.
""" """
user = User.query.filter().first() user = User.query.filter().first()
pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')])) pc = d.Desktop(
**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')])
)
pc.owner_id = user.id pc.owner_id = user.id
with raises(ResourceNotFound): with raises(ResourceNotFound):
Sync().execute_register(pc) Sync().execute_register(pc)
@ -345,7 +377,8 @@ def test_sync_execute_register_tag_linked_same_device():
db.session.commit() db.session.commit()
pc = d.Desktop( pc = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object **yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
pc.tags.add(Tag(id='foo')) 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
@ -369,7 +402,8 @@ def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
db.session.commit() db.session.commit()
pc1 = d.Desktop( pc1 = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object **yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
pc1.tags.add(Tag(id='foo-1')) pc1.tags.add(Tag(id='foo-1'))
pc1.tags.add(Tag(id='foo-2')) pc1.tags.add(Tag(id='foo-2'))
with raises(MismatchBetweenTags): with raises(MismatchBetweenTags):
@ -393,7 +427,8 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
db.session.commit() db.session.commit()
pc1 = d.Desktop( pc1 = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object **yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
pc1.tags.add(Tag(id='foo-2')) pc1.tags.add(Tag(id='foo-2'))
with raises(MismatchBetweenTagsAndHid): with raises(MismatchBetweenTagsAndHid):
Sync().execute_register(pc1) Sync().execute_register(pc1)
@ -404,22 +439,36 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
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() g.user = User.query.one()
pc = d.Desktop(model='p1mo', pc = d.Desktop(
manufacturer='p1ma', model='p1mo',
serial_number='p1s', manufacturer='p1ma',
chassis=ComputerChassis.Tower, serial_number='p1s',
owner_id=user.user['id']) chassis=ComputerChassis.Tower,
pc.components = OrderedSet([ owner_id=user.user['id'],
d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s', )
owner_id=user.user['id']), pc.components = OrderedSet(
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']) [
]) d.NetworkAdapter(
model='c1mo',
manufacturer='c1ma',
serial_number='c1s',
owner_id=user.user['id'],
),
d.GraphicCard(
model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']
),
]
)
db.session.add(pc) db.session.add(pc)
# todo test is an abstract class. replace with another one # todo test is an abstract class. replace with another one
db.session.add(TestConnectivity(device=pc, db.session.add(
severity=Severity.Info, TestConnectivity(
agent=Person(name='Timmy'), device=pc,
author=User(email='bar@bar.com'))) severity=Severity.Info,
agent=Person(name='Timmy'),
author=User(email='bar@bar.com'),
)
)
db.session.commit() db.session.commit()
pc_api, _ = user.get(res=d.Device, item=pc.devicehub_id) pc_api, _ = user.get(res=d.Device, item=pc.devicehub_id)
assert len(pc_api['actions']) == 1 assert len(pc_api['actions']) == 1
@ -427,10 +476,14 @@ def test_get_device(user: UserClient):
assert pc_api['actions'][0]['device'] == pc.id assert pc_api['actions'][0]['device'] == pc.id
assert pc_api['actions'][0]['severity'] == 'Info' assert pc_api['actions'][0]['severity'] == 'Info'
assert UUID(pc_api['actions'][0]['author']) assert UUID(pc_api['actions'][0]['author'])
assert 'actions_components' not in pc_api, 'actions_components are internal use only' assert (
'actions_components' not in pc_api
), 'actions_components are internal use only'
assert 'actions_one' not in pc_api, 'they are internal use only' assert 'actions_one' not in pc_api, 'they are internal use only'
assert 'author' not in pc_api assert 'author' not in pc_api
assert tuple(c['id'] for c in pc_api['components']) == tuple(c.id for c in pc.components) assert tuple(c['id'] for c in pc_api['components']) == tuple(
c.id for c in pc.components
)
assert pc_api['hid'] == 'desktop-p1ma-p1mo-p1s' assert pc_api['hid'] == 'desktop-p1ma-p1mo-p1s'
assert pc_api['model'] == 'p1mo' assert pc_api['model'] == 'p1mo'
assert pc_api['manufacturer'] == 'p1ma' assert pc_api['manufacturer'] == 'p1ma'
@ -443,41 +496,59 @@ def test_get_device(user: UserClient):
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() g.user = User.query.one()
pc = d.Desktop(model='p1mo', pc = d.Desktop(
manufacturer='p1ma', model='p1mo',
serial_number='p1s', manufacturer='p1ma',
chassis=ComputerChassis.Tower, serial_number='p1s',
owner_id=user.user['id']) chassis=ComputerChassis.Tower,
pc.components = OrderedSet([ owner_id=user.user['id'],
d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s', )
owner_id=user.user['id']), pc.components = OrderedSet(
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500, [
owner_id=user.user['id']) d.NetworkAdapter(
]) model='c1mo',
pc1 = d.Desktop(model='p2mo', manufacturer='c1ma',
manufacturer='p2ma', serial_number='c1s',
serial_number='p2s', owner_id=user.user['id'],
chassis=ComputerChassis.Tower, ),
owner_id=user.user['id']) d.GraphicCard(
pc2 = d.Laptop(model='p3mo', model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']
manufacturer='p3ma', ),
serial_number='p3s', ]
chassis=ComputerChassis.Netbook, )
owner_id=user.user['id']) pc1 = d.Desktop(
model='p2mo',
manufacturer='p2ma',
serial_number='p2s',
chassis=ComputerChassis.Tower,
owner_id=user.user['id'],
)
pc2 = d.Laptop(
model='p3mo',
manufacturer='p3ma',
serial_number='p3s',
chassis=ComputerChassis.Netbook,
owner_id=user.user['id'],
)
db.session.add_all((pc, pc1, pc2)) db.session.add_all((pc, pc1, pc2))
db.session.commit() db.session.commit()
devices, _ = user.get(res=d.Device) devices, _ = user.get(res=d.Device)
ids = (pc.id, pc1.id, pc2.id, pc.components[0].id, pc.components[1].id) ids = (pc.id, pc1.id, pc2.id, pc.components[0].id, pc.components[1].id)
assert tuple(dev['id'] for dev in devices['items']) == ids assert tuple(dev['id'] for dev in devices['items']) == ids
assert tuple(dev['type'] for dev in devices['items']) == ( assert tuple(dev['type'] for dev in devices['items']) == (
d.Desktop.t, d.Desktop.t, d.Laptop.t, d.NetworkAdapter.t, d.GraphicCard.t d.Desktop.t,
d.Desktop.t,
d.Laptop.t,
d.NetworkAdapter.t,
d.GraphicCard.t,
) )
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_get_device_permissions(app: Devicehub, user: UserClient, user2: UserClient, def test_get_device_permissions(
client: Client): app: Devicehub, user: UserClient, user2: UserClient, client: Client
):
"""Checks GETting a d.Desktop with its components.""" """Checks GETting a d.Desktop with its components."""
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
@ -529,12 +600,12 @@ def test_get_devices_unassigned(user: UserClient):
assert len(devices['items']) == 2 assert len(devices['items']) == 2
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
device_id = devices['items'][0]['id'] device_id = devices['items'][0]['id']
my_lot, _ = user.post(({'name': 'My_lot'}), res=Lot) my_lot, _ = user.post(({'name': 'My_lot'}), res=Lot)
lot, _ = user.post({}, lot, _ = user.post(
res=Lot, {}, res=Lot, item='{}/devices'.format(my_lot['id']), query=[('id', device_id)]
item='{}/devices'.format(my_lot['id']), )
query=[('id', device_id)])
lot = Lot.query.filter_by(id=lot['id']).one() lot = Lot.query.filter_by(id=lot['id']).one()
assert next(iter(lot.devices)).id == device_id assert next(iter(lot.devices)).id == device_id
@ -554,13 +625,15 @@ def test_get_devices_unassigned(user: UserClient):
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_computer_monitor(): def test_computer_monitor():
m = d.ComputerMonitor(technology=DisplayTech.LCD, m = d.ComputerMonitor(
manufacturer='foo', technology=DisplayTech.LCD,
model='bar', manufacturer='foo',
serial_number='foo-bar', model='bar',
resolution_width=1920, serial_number='foo-bar',
resolution_height=1080, resolution_width=1920,
size=14.5) resolution_height=1080,
size=14.5,
)
db.session.add(m) db.session.add(m)
db.session.commit() db.session.commit()
@ -568,9 +641,11 @@ def test_computer_monitor():
@pytest.mark.mvp @pytest.mark.mvp
def test_manufacturer(user: UserClient): def test_manufacturer(user: UserClient):
m, r = user.get(res='Manufacturer', query=[('search', 'asus')]) m, r = user.get(res='Manufacturer', query=[('search', 'asus')])
assert m == {'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]} assert m == {
'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]
}
assert r.cache_control.public assert r.cache_control.public
assert r.expires > datetime.datetime.now() assert r.expires.timestamp() > datetime.datetime.now().timestamp()
@pytest.mark.mvp @pytest.mark.mvp
@ -591,12 +666,20 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116' assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116'
assert pc.ram_size == 1024 assert pc.ram_size == 1024
assert pc.data_storage_size == 152627 assert pc.data_storage_size == 152627
assert pc.graphic_card_model == 'mobile 945gse express integrated graphics controller' assert (
pc.graphic_card_model
== 'mobile 945gse express integrated graphics controller'
)
assert pc.processor_model == 'intel atom cpu n270 @ 1.60ghz' assert pc.processor_model == 'intel atom cpu n270 @ 1.60ghz'
net = next(c for c in pc.components if isinstance(c, d.NetworkAdapter)) net = next(c for c in pc.components if isinstance(c, d.NetworkAdapter))
assert format(net) == 'NetworkAdapter 5: model ar8121/ar8113/ar8114 ' \ assert (
'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d' format(net) == 'NetworkAdapter 5: model ar8121/ar8113/ar8114 '
assert format(net, 't') == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet' 'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d'
)
assert (
format(net, 't')
== 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet'
)
assert format(net, 's') == 'qualcomm atheros 00:24:8C:7F:CF:2D 100 Mbps' assert format(net, 's') == 'qualcomm atheros 00:24:8C:7F:CF:2D 100 Mbps'
hdd = next(c for c in pc.components if isinstance(c, d.DataStorage)) hdd = next(c for c in pc.components if isinstance(c, d.DataStorage))
assert format(hdd) == 'HardDrive 10: model st9160310as, S/N 5sv4tqa6' assert format(hdd) == 'HardDrive 10: model st9160310as, S/N 5sv4tqa6'
@ -638,8 +721,12 @@ def test_networking_model(user: UserClient):
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_cooking_mixer(user: UserClient): def test_cooking_mixer(user: UserClient):
mixer = d.Mixer(serial_number='foo', model='bar', manufacturer='foobar', mixer = d.Mixer(
owner_id=user.user['id']) serial_number='foo',
model='bar',
manufacturer='foobar',
owner_id=user.user['id'],
)
db.session.add(mixer) db.session.add(mixer)
db.session.commit() db.session.commit()
@ -652,12 +739,12 @@ def test_cooking_mixer_api(user: UserClient):
'serialNumber': 'foo', 'serialNumber': 'foo',
'model': 'bar', 'model': 'bar',
'manufacturer': 'foobar', 'manufacturer': 'foobar',
'type': 'Mixer' 'type': 'Mixer',
}, },
'version': '11.0', 'version': '11.0',
'software': SnapshotSoftware.Web.name 'software': SnapshotSoftware.Web.name,
}, },
res=m.Snapshot res=m.Snapshot,
) )
mixer, _ = user.get(res=d.Device, item=snapshot['device']['id']) mixer, _ = user.get(res=d.Device, item=snapshot['device']['id'])
assert mixer['type'] == 'Mixer' assert mixer['type'] == 'Mixer'
@ -673,14 +760,19 @@ def test_hid_with_mac(app: Devicehub, user: UserClient):
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one()
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
@pytest.mark.mvp @pytest.mark.mvp
def test_hid_without_mac(app: Devicehub, user: UserClient): def test_hid_without_mac(app: Devicehub, user: UserClient):
"""Checks hid without mac.""" """Checks hid without mac."""
snapshot = yaml2json('asus-eee-1000h.snapshot.11') snapshot = yaml2json('asus-eee-1000h.snapshot.11')
snapshot['components'] = [c for c in snapshot['components'] if c['type'] != 'NetworkAdapter'] snapshot['components'] = [
c for c in snapshot['components'] if c['type'] != 'NetworkAdapter'
]
snap, _ = user.post(json_encode(snapshot), res=m.Snapshot) snap, _ = user.post(json_encode(snapshot), res=m.Snapshot)
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
@ -709,7 +801,10 @@ def test_hid_with_2networkadapters(app: Devicehub, user: UserClient):
devices, _ = user.get(res=d.Device) devices, _ = user.get(res=d.Device)
laptop = devices['items'][0] laptop = devices['items'][0]
assert laptop['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' assert (
laptop['hid']
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2 assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2
@ -726,14 +821,20 @@ def test_hid_with_2network_and_drop_no_mac_in_hid(app: Devicehub, user: UserClie
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one()
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb' snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb'
snapshot['components'] = [c for c in snapshot['components'] if c != network] snapshot['components'] = [c for c in snapshot['components'] if c != network]
user.post(json_encode(snapshot), res=m.Snapshot) user.post(json_encode(snapshot), res=m.Snapshot)
devices, _ = user.get(res=d.Device) devices, _ = user.get(res=d.Device)
laptop = devices['items'][0] laptop = devices['items'][0]
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2 assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2
assert len([c for c in laptop['components'] if c['type'] == 'NetworkAdapter']) == 1 assert len([c for c in laptop['components'] if c['type'] == 'NetworkAdapter']) == 1
@ -752,7 +853,10 @@ def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient)
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one()
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
# we drop the network card then is used for to build the hid # we drop the network card then is used for to build the hid
snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb' snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb'
@ -762,19 +866,25 @@ def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient)
laptops = [c for c in devices['items'] if c['type'] == 'Laptop'] laptops = [c for c in devices['items'] if c['type'] == 'Laptop']
assert len(laptops) == 4 assert len(laptops) == 4
hids = [laptops[0]['hid'], laptops[2]['hid']] hids = [laptops[0]['hid'], laptops[2]['hid']]
proof_hid = ['laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', proof_hid = [
'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'] 'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d',
]
assert all([h in proof_hid for h in hids]) assert all([h in proof_hid for h in hids])
# we drop all network cards # we drop all network cards
snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abc' snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abc'
snapshot['components'] = [c for c in snapshot['components'] if c not in [network, network2]] snapshot['components'] = [
c for c in snapshot['components'] if c not in [network, network2]
]
user.post(json_encode(snapshot), res=m.Snapshot) user.post(json_encode(snapshot), res=m.Snapshot)
devices, _ = user.get(res=d.Device) devices, _ = user.get(res=d.Device)
laptops = [c for c in devices['items'] if c['type'] == 'Laptop'] laptops = [c for c in devices['items'] if c['type'] == 'Laptop']
assert len(laptops) == 4 assert len(laptops) == 4
hids = [laptops[0]['hid'], laptops[2]['hid']] hids = [laptops[0]['hid'], laptops[2]['hid']]
proof_hid = ['laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', proof_hid = [
'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d', 'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116'] 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116',
]
assert all([h in proof_hid for h in hids]) assert all([h in proof_hid for h in hids])

View File

@ -21,10 +21,12 @@ from tests.conftest import TestConfig
class NoExcCliRunner(click.testing.CliRunner): class NoExcCliRunner(click.testing.CliRunner):
"""Runner that interfaces with the Devicehub CLI.""" """Runner that interfaces with the Devicehub CLI."""
def invoke(self, *args, input=None, env=None, catch_exceptions=False, color=False, def invoke(
**extra): self, *args, input=None, env=None, catch_exceptions=False, color=False, **extra
r = super().invoke(ereuse_devicehub.cli.cli, ):
args, input, env, catch_exceptions, color, **extra) r = super().invoke(
ereuse_devicehub.cli.cli, args, input, env, catch_exceptions, color, **extra
)
assert r.exit_code == 0, 'CLI code {}: {}'.format(r.exit_code, r.output) assert r.exit_code == 0, 'CLI code {}: {}'.format(r.exit_code, r.output)
return r return r
@ -69,16 +71,26 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2):
""" """
# Create first DB # Create first DB
cli.inv('tdb1') cli.inv('tdb1')
cli.invoke('inv', 'add', cli.invoke(
'-n', 'Test DB1', 'inv',
'-on', 'ACME DB1', 'add',
'-oi', 'acme-id', '-n',
'-tu', 'https://example.com', 'Test DB1',
'-tt', '3c66a6ad-22de-4db6-ac46-d8982522ec40', '-on',
'--common') 'ACME DB1',
'-oi',
'acme-id',
'-tu',
'https://example.com',
'-tt',
'3c66a6ad-22de-4db6-ac46-d8982522ec40',
'--common',
)
# Create an user for first DB # Create an user for first DB
cli.invoke('user', 'add', 'foo@foo.com', '-a', 'Foo', '-c', 'ES', '-p', 'Such password') cli.invoke(
'user', 'add', 'foo@foo.com', '-a', 'Foo', '-c', 'ES', '-p', 'Such password'
)
with tdb1.app_context(): with tdb1.app_context():
# There is a row for the inventory # There is a row for the inventory
@ -98,12 +110,20 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2):
cli.inv('tdb2') cli.inv('tdb2')
# Create a second DB # Create a second DB
# Note how we don't create common anymore # Note how we don't create common anymore
cli.invoke('inv', 'add', cli.invoke(
'-n', 'Test DB2', 'inv',
'-on', 'ACME DB2', 'add',
'-oi', 'acme-id-2', '-n',
'-tu', 'https://example.com', 'Test DB2',
'-tt', 'fbad1c08-ffdc-4a61-be49-464962c186a8') '-on',
'ACME DB2',
'-oi',
'acme-id-2',
'-tu',
'https://example.com',
'-tt',
'fbad1c08-ffdc-4a61-be49-464962c186a8',
)
# Create an user for with access for both DB # Create an user for with access for both DB
cli.invoke('user', 'add', 'bar@bar.com', '-a', 'Bar', '-p', 'Wow password') cli.invoke('user', 'add', 'bar@bar.com', '-a', 'Bar', '-p', 'Wow password')
@ -144,5 +164,6 @@ def test_create_existing_inventory(cli, tdb1):
cli.invoke('inv', 'add', '--common') cli.invoke('inv', 'add', '--common')
with tdb1.app_context(): with tdb1.app_context():
assert db.has_schema('tdb1') assert db.has_schema('tdb1')
with pytest.raises(AssertionError, message='Schema tdb1 already exists.'): with pytest.raises(AssertionError):
cli.invoke('inv', 'add', '--common') cli.invoke('inv', 'add', '--common')
pytest.fail('Schema tdb1 already exists.')

View File

@ -356,7 +356,6 @@ def test_label_details(user3: UserClientFlask):
user3.post(uri, data=data) user3.post(uri, data=data)
body, status = user3.get('/labels/tag1/') body, status = user3.get('/labels/tag1/')
assert "tag1" in body
assert "Print Label" in body assert "Print Label" in body
@ -562,7 +561,6 @@ def test_update_monitor(user3: UserClientFlask):
data = { data = {
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'type': "Monitor", 'type': "Monitor",
'phid': '1',
'serial_number': "AAAAB", 'serial_number': "AAAAB",
'model': "LCD 43 b", 'model': "LCD 43 b",
'manufacturer': "Samsung", 'manufacturer': "Samsung",
@ -575,8 +573,9 @@ def test_update_monitor(user3: UserClientFlask):
} }
body, status = user3.post(uri, data=data) body, status = user3.post(uri, data=data)
assert status == '200 OK' assert status == '200 OK'
assert 'Error, exist one Placeholder device with this PHID' in body # assert 'Error, exist one Placeholder device with this PHID' in body
dev = Device.query.one() dev = Device.query.all()[0]
assert Device.query.count() == 2
assert dev.type == 'Monitor' assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b2" assert dev.placeholder.id_device_supplier == "b2"
assert dev.hid == 'monitor-samsung-lc27t55-aaaab' assert dev.hid == 'monitor-samsung-lc27t55-aaaab'
@ -597,7 +596,6 @@ def test_add_2_monitor(user3: UserClientFlask):
data = { data = {
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'type': "Monitor", 'type': "Monitor",
'phid': "AAB",
'serial_number': "AAAAB", 'serial_number': "AAAAB",
'model': "LC27T55", 'model': "LC27T55",
'manufacturer': "Samsung", 'manufacturer': "Samsung",
@ -619,7 +617,7 @@ def test_add_2_monitor(user3: UserClientFlask):
assert typ == 'Monitor' assert typ == 'Monitor'
assert dev.placeholder.id_device_supplier == "b1" assert dev.placeholder.id_device_supplier == "b1"
assert dev.hid == 'monitor-samsung-lc27t55-aaaab' assert dev.hid == 'monitor-samsung-lc27t55-aaaab'
assert phid == 'AAB' assert phid == '1'
assert dhid == 'O48N2' assert dhid == 'O48N2'
assert dev.model == 'lc27t55' assert dev.model == 'lc27t55'
assert dev.placeholder.pallet == "l34" assert dev.placeholder.pallet == "l34"
@ -737,35 +735,6 @@ def test_add_with_ammount_laptops(user3: UserClientFlask):
assert Device.query.count() == num assert Device.query.count() == num
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_laptop_duplicate(user3: UserClientFlask):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert Device.query.count() == 1
body, status = user3.post(uri, data=data)
assert 'Error, exist one Placeholder device with this PHID' in body
assert Device.query.count() == 1
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_filter_monitor(user3: UserClientFlask): def test_filter_monitor(user3: UserClientFlask):
@ -1405,6 +1374,7 @@ def test_wb_settings_register(user3: UserClientFlask):
def test_create_transfer(user3: UserClientFlask): def test_create_transfer(user3: UserClientFlask):
user3.get('/inventory/lot/add/') user3.get('/inventory/lot/add/')
lot_name = 'lot1' lot_name = 'lot1'
lot_name2 = 'lot2'
data = { data = {
'name': lot_name, 'name': lot_name,
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
@ -1421,13 +1391,14 @@ def test_create_transfer(user3: UserClientFlask):
assert 'Description' in body assert 'Description' in body
assert 'Save' in body assert 'Save' in body
data = {'csrf_token': generate_csrf(), 'code': 'AAA'} data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name2}
body, status = user3.post(uri, data=data) body, status = user3.post(uri, data=data)
assert status == '200 OK' assert status == '200 OK'
assert 'Transfer created successfully!' in body assert 'Transfer created successfully!' in body
assert 'Delete Lot' in body assert 'Delete Lot' in body
assert 'Incoming Lot' in body assert 'Incoming Lot' in body
assert lot_name2 in body
@pytest.mark.mvp @pytest.mark.mvp
@ -1436,6 +1407,8 @@ def test_edit_transfer(user3: UserClientFlask):
# create lot # create lot
user3.get('/inventory/lot/add/') user3.get('/inventory/lot/add/')
lot_name = 'lot1' lot_name = 'lot1'
lot_name2 = 'lot2'
lot_name3 = 'lot3'
data = { data = {
'name': lot_name, 'name': lot_name,
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
@ -1453,12 +1426,13 @@ def test_edit_transfer(user3: UserClientFlask):
# create new incoming lot # create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/' uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'} data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name2}
body, status = user3.post(uri, data=data) body, status = user3.post(uri, data=data)
assert 'Transfer (<span class="text-success">Open</span>)' in body assert 'Transfer (<span class="text-success">Open</span>)' in body
assert '<i class="bi bi-trash"></i> Delete Lot' in body assert '<i class="bi bi-trash"></i> Delete Lot' in body
lot = Lot.query.filter()[1] lot = Lot.query.filter()[1]
assert lot.transfer is not None assert lot.transfer is not None
assert lot_name2 in body
# edit transfer with errors # edit transfer with errors
lot_id = lot.id lot_id = lot.id
@ -1467,6 +1441,7 @@ def test_edit_transfer(user3: UserClientFlask):
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'code': 'AAA', 'code': 'AAA',
'description': 'one one one', 'description': 'one one one',
'lot_name': lot_name3,
'date': datetime.datetime.now().date() + datetime.timedelta(15), 'date': datetime.datetime.now().date() + datetime.timedelta(15),
} }
body, status = user3.post(uri, data=data) body, status = user3.post(uri, data=data)
@ -1481,6 +1456,7 @@ def test_edit_transfer(user3: UserClientFlask):
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'code': 'AAA', 'code': 'AAA',
'description': 'one one one', 'description': 'one one one',
'lot_name': lot_name3,
'date': datetime.datetime.now().date() - datetime.timedelta(15), 'date': datetime.datetime.now().date() - datetime.timedelta(15),
} }
body, status = user3.post(uri, data=data) body, status = user3.post(uri, data=data)
@ -1489,6 +1465,7 @@ def test_edit_transfer(user3: UserClientFlask):
assert 'one one one' in body assert 'one one one' in body
assert '<i class="bi bi-trash"></i> Delete Lot' not in body assert '<i class="bi bi-trash"></i> Delete Lot' not in body
assert 'Transfer (<span class="text-danger">Closed</span>)' in body assert 'Transfer (<span class="text-danger">Closed</span>)' in body
assert lot_name3 in body
@pytest.mark.mvp @pytest.mark.mvp
@ -1507,7 +1484,7 @@ def test_edit_deliverynote(user3: UserClientFlask):
# create new incoming lot # create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/' uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'} data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
user3.post(uri, data=data) user3.post(uri, data=data)
lot = Lot.query.filter()[1] lot = Lot.query.filter()[1]
lot_id = lot.id lot_id = lot.id
@ -1548,7 +1525,7 @@ def test_edit_receivernote(user3: UserClientFlask):
# create new incoming lot # create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/' uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'} data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
user3.post(uri, data=data) user3.post(uri, data=data)
lot = Lot.query.filter()[1] lot = Lot.query.filter()[1]
lot_id = lot.id lot_id = lot.id
@ -1589,7 +1566,7 @@ def test_edit_notes_with_closed_transfer(user3: UserClientFlask):
# create new incoming lot # create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/' uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'} data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
user3.post(uri, data=data) user3.post(uri, data=data)
lot = Lot.query.filter()[1] lot = Lot.query.filter()[1]
lot_id = lot.id lot_id = lot.id
@ -1698,6 +1675,7 @@ def test_export_lots(user3: UserClientFlask):
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_export_snapshot_json(user3: UserClientFlask): def test_export_snapshot_json(user3: UserClientFlask):
# ??
file_name = 'real-eee-1001pxd.snapshot.13.json' file_name = 'real-eee-1001pxd.snapshot.13.json'
snap = create_device(user3, file_name) snap = create_device(user3, file_name)
@ -1729,7 +1707,7 @@ def test_add_placeholder_excel(user3: UserClientFlask):
assert Device.query.count() == 3 assert Device.query.count() == 3
dev = Device.query.first() dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678' assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A' assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT' assert dev.placeholder.id_device_supplier == 'TTT'
@ -1755,7 +1733,7 @@ def test_add_placeholder_csv(user3: UserClientFlask):
assert Device.query.count() == 3 assert Device.query.count() == 3
dev = Device.query.first() dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678' assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A' assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT' assert dev.placeholder.id_device_supplier == 'TTT'
@ -1781,7 +1759,7 @@ def test_add_placeholder_ods(user3: UserClientFlask):
assert Device.query.count() == 3 assert Device.query.count() == 3
dev = Device.query.first() dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678' assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A' assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT' assert dev.placeholder.id_device_supplier == 'TTT'
@ -1809,10 +1787,11 @@ def test_add_placeholder_office_open_xml(user3: UserClientFlask):
assert Device.query.count() == 3 assert Device.query.count() == 3
dev = Device.query.first() dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678' assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A' assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT' assert dev.placeholder.id_device_supplier == 'TTT'
assert dev.placeholder.id_device_internal == 'AA'
@pytest.mark.mvp @pytest.mark.mvp
@ -1921,8 +1900,8 @@ def test_placeholder_log_manual_edit(user3: UserClientFlask):
data = { data = {
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'type': "Laptop", 'type': "Laptop",
'phid': 'ace',
'serial_number': "AAAAB", 'serial_number': "AAAAB",
'part_number': "AAAAB",
'model': "LC27T55", 'model': "LC27T55",
'manufacturer': "Samsung", 'manufacturer': "Samsung",
'generation': 1, 'generation': 1,
@ -1930,9 +1909,13 @@ def test_placeholder_log_manual_edit(user3: UserClientFlask):
'height': 0.1, 'height': 0.1,
'depth': 0.1, 'depth': 0.1,
'id_device_supplier': "b2", 'id_device_supplier': "b2",
'id_device_internal': "b2i",
} }
user3.post(uri, data=data) user3.post(uri, data=data)
dev = Device.query.one() dev = Device.query.one()
plz = Placeholder.query.first()
assert plz.id_device_supplier == "b2"
assert plz.id_device_internal == "b2i"
uri = '/inventory/device/edit/{}/'.format(dev.devicehub_id) uri = '/inventory/device/edit/{}/'.format(dev.devicehub_id)
user3.get(uri) user3.get(uri)
@ -1948,16 +1931,20 @@ def test_placeholder_log_manual_edit(user3: UserClientFlask):
'height': 0.1, 'height': 0.1,
'depth': 0.1, 'depth': 0.1,
'id_device_supplier': "a2", 'id_device_supplier': "a2",
'id_device_internal': "a2i",
} }
user3.post(uri, data=data) user3.post(uri, data=data)
plz = Placeholder.query.first()
assert plz.id_device_supplier == "a2"
assert plz.id_device_internal == "a2i"
uri = '/inventory/placeholder-logs/' uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri) body, status = user3.get(uri)
assert status == '200 OK' assert status == '200 OK'
assert "Placeholder Logs" in body assert "Placeholder Logs" in body
assert "Web form" in body
assert "ace" in body
assert "Update" in body assert "Update" in body
assert "Web form" in body
assert "1" in body
assert dev.devicehub_id in body assert dev.devicehub_id in body
assert "" in body assert "" in body
assert "CSV" not in body assert "CSV" not in body
@ -1980,7 +1967,7 @@ def test_placeholder_log_excel_new(user3: UserClientFlask):
} }
user3.post(uri, data=data, content_type="multipart/form-data") user3.post(uri, data=data, content_type="multipart/form-data")
dev = Device.query.first() dev = Device.query.first()
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
uri = '/inventory/placeholder-logs/' uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri) body, status = user3.get(uri)
@ -1989,7 +1976,6 @@ def test_placeholder_log_excel_new(user3: UserClientFlask):
assert dev.placeholder.phid in body assert dev.placeholder.phid in body
assert dev.devicehub_id in body assert dev.devicehub_id in body
assert "Web form" not in body assert "Web form" not in body
assert "Update" not in body
assert "New device" in body assert "New device" in body
assert "" in body assert "" in body
assert "CSV" not in body assert "CSV" not in body
@ -2023,7 +2009,7 @@ def test_placeholder_log_excel_update(user3: UserClientFlask):
user3.post(uri, data=data, content_type="multipart/form-data") user3.post(uri, data=data, content_type="multipart/form-data")
dev = Device.query.first() dev = Device.query.first()
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
uri = '/inventory/placeholder-logs/' uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri) body, status = user3.get(uri)
@ -2032,7 +2018,6 @@ def test_placeholder_log_excel_update(user3: UserClientFlask):
assert dev.placeholder.phid in body assert dev.placeholder.phid in body
assert dev.devicehub_id in body assert dev.devicehub_id in body
assert "Web form" not in body assert "Web form" not in body
assert "Update" in body
assert "New device" in body assert "New device" in body
assert "" in body assert "" in body
assert "CSV" in body assert "CSV" in body
@ -2070,7 +2055,7 @@ def test_add_placeholder_excel_from_lot(user3: UserClientFlask):
assert Device.query.count() == 3 assert Device.query.count() == 3
dev = Device.query.first() dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678' assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123' assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A' assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT' assert dev.placeholder.id_device_supplier == 'TTT'
@ -2097,7 +2082,6 @@ def test_add_new_placeholder_from_lot(user3: UserClientFlask):
data = { data = {
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'type': "Laptop", 'type': "Laptop",
'phid': 'ace',
'serial_number': "AAAAB", 'serial_number': "AAAAB",
'model': "LC27T55", 'model': "LC27T55",
'manufacturer': "Samsung", 'manufacturer': "Samsung",
@ -2110,7 +2094,7 @@ def test_add_new_placeholder_from_lot(user3: UserClientFlask):
user3.post(uri, data=data) user3.post(uri, data=data)
dev = Device.query.one() dev = Device.query.one()
assert dev.hid == 'laptop-samsung-lc27t55-aaaab' assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == 'ace' assert dev.placeholder.phid == '1'
assert len(lot.devices) == 1 assert len(lot.devices) == 1
@ -2124,7 +2108,6 @@ def test_manual_binding(user3: UserClientFlask):
data = { data = {
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'type': "Laptop", 'type': "Laptop",
'phid': 'sid',
'serial_number': "AAAAB", 'serial_number': "AAAAB",
'model': "LC27T55", 'model': "LC27T55",
'manufacturer': "Samsung", 'manufacturer': "Samsung",
@ -2136,7 +2119,7 @@ def test_manual_binding(user3: UserClientFlask):
user3.post(uri, data=data) user3.post(uri, data=data)
dev = Device.query.one() dev = Device.query.one()
assert dev.hid == 'laptop-samsung-lc27t55-aaaab' assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == 'sid' assert dev.placeholder.phid == '1'
assert dev.placeholder.is_abstract is False assert dev.placeholder.is_abstract is False
# add device from wb # add device from wb
@ -2155,7 +2138,7 @@ def test_manual_binding(user3: UserClientFlask):
# page binding # page binding
dhid = dev_wb.dhid dhid = dev_wb.dhid
uri = f'/inventory/binding/{dhid}/sid/' uri = f'/inventory/binding/{dhid}/1/'
body, status = user3.get(uri) body, status = user3.get(uri)
assert status == '200 OK' assert status == '200 OK'
assert 'sid' in body assert 'sid' in body
@ -2174,11 +2157,18 @@ def test_manual_binding(user3: UserClientFlask):
assert txt in body assert txt in body
# check new structure # check new structure
assert dev_wb.binding.phid == 'sid' assert dev_wb.binding.phid == '1'
assert dev_wb.binding.device == dev assert dev_wb.binding.device == dev
assert dev_wb.phid() == dev.phid()
assert dev_wb.is_abstract() == dev.is_abstract() == 'Twin'
# assert dev_wb.
assert Placeholder.query.filter_by(id=old_placeholder.id).first() is None assert Placeholder.query.filter_by(id=old_placeholder.id).first() is None
assert Device.query.filter_by(id=old_placeholder.device.id).first() is None assert Device.query.filter_by(id=old_placeholder.device.id).first() is None
body_real, status = user3.get(f'/devices/{dhid_real}')
body_abstract, status = user3.get(f'/devices/{dhid_abstract}')
assert body_real == body_abstract
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
@ -2237,7 +2227,6 @@ def test_unbinding(user3: UserClientFlask):
data = { data = {
'csrf_token': generate_csrf(), 'csrf_token': generate_csrf(),
'type': "Laptop", 'type': "Laptop",
'phid': 'sid',
'serial_number': "AAAAB", 'serial_number': "AAAAB",
'model': "LC27T55", 'model': "LC27T55",
'manufacturer': "Samsung", 'manufacturer': "Samsung",
@ -2262,20 +2251,22 @@ def test_unbinding(user3: UserClientFlask):
# page binding # page binding
dhid = dev_wb.dhid dhid = dev_wb.dhid
uri = f'/inventory/binding/{dhid}/sid/' uri = f'/inventory/binding/{dhid}/1/'
user3.get(uri) user3.get(uri)
# action binding # action binding
assert Placeholder.query.count() == 11
assert dev.placeholder.binding is None assert dev.placeholder.binding is None
user3.post(uri, data={}) user3.post(uri, data={})
assert dev.placeholder.binding == dev_wb assert dev.placeholder.binding == dev_wb
assert Placeholder.query.count() == 1
dhid = dev.dhid dhid = dev.dhid
# action unbinding # action unbinding
uri = '/inventory/unbinding/sid/' uri = '/inventory/unbinding/1/'
body, status = user3.post(uri, data={}) body, status = user3.post(uri, data={})
assert status == '200 OK' assert status == '200 OK'
txt = f'Device with PHID:&#34;sid&#34; and DHID: {dhid} unbind successfully!' txt = f'Device with PHID:&#34;1&#34; and DHID: {dhid} unbind successfully!'
assert txt in body assert txt in body
# assert 'Device &#34;sid&#34; unbind successfully!' in body # assert 'Device &#34;sid&#34; unbind successfully!' in body
@ -2292,6 +2283,7 @@ def test_unbinding(user3: UserClientFlask):
assert Device.query.filter_by(id=dev_wb.binding.device.id).first() assert Device.query.filter_by(id=dev_wb.binding.device.id).first()
assert Device.query.filter_by(id=dev.id).first() assert Device.query.filter_by(id=dev.id).first()
assert Placeholder.query.filter_by(id=dev.placeholder.id).first() assert Placeholder.query.filter_by(id=dev.placeholder.id).first()
assert Placeholder.query.count() == 11
@pytest.mark.mvp @pytest.mark.mvp
@ -2400,8 +2392,8 @@ def test_bug_3821_binding(user3: UserClientFlask):
user3.post(uri, data=data) user3.post(uri, data=data)
dev = Device.query.one() dev = Device.query.one()
dhid = dev.dhid dhid = dev.dhid
assert dev.phid() == 'sid' assert dev.phid() == '1'
uri = f'/inventory/binding/{dhid}/sid/' uri = f'/inventory/binding/{dhid}/1/'
body, status = user3.get(uri) body, status = user3.get(uri)
assert status == '200 OK' assert status == '200 OK'
assert 'is not a Snapshot device!' in body assert 'is not a Snapshot device!' in body
@ -2430,7 +2422,7 @@ def test_bug_3831_documents(user3: UserClientFlask):
uri = f'/inventory/lot/{lot_id}/transfer/incoming/' uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
user3.get(uri) user3.get(uri)
data = {'csrf_token': generate_csrf(), 'code': 'AAA'} data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
body, status = user3.post(uri, data=data) body, status = user3.post(uri, data=data)
assert status == '200 OK' assert status == '200 OK'
@ -2457,3 +2449,132 @@ def test_bug_3831_documents(user3: UserClientFlask):
uri = f'/inventory/lot/{lot_id}/trade-document/add/' uri = f'/inventory/lot/{lot_id}/trade-document/add/'
# body, status = user3.post(uri, data=data, content_type="multipart/form-data") # body, status = user3.post(uri, data=data, content_type="multipart/form-data")
# assert status == '200 OK' # assert status == '200 OK'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_hdd_filter(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
hdds = Device.query.filter_by(type='HardDrive').all()
for hdd in hdds:
hdd.parent = None
db.session.commit()
csrf = generate_csrf()
uri = f'/inventory/device/?filter=All+DataStorage&csrf_token={csrf}'
body, status = user3.get(uri)
assert status == '200 OK'
for hdd in hdds:
assert hdd.dhid in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_kangaroo(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
body, status = user3.get('/workbench/')
assert status == '200 OK'
pc = Device.query.filter_by(type='Laptop').first()
data = {
'csrf_token': generate_csrf(),
'phid': pc.phid(),
}
body, status = user3.post('/workbench/', data=data)
assert status == '200 OK'
assert pc.phid() in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_drop_kangaroo(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
pc = Device.query.filter_by(type='Laptop').first()
phid = 'AAA'
pc.placeholder.phid = phid
db.session.commit()
body, status = user3.get('/workbench/')
assert phid not in body
data = {
'csrf_token': generate_csrf(),
'phid': phid,
}
body, status = user3.post('/workbench/', data=data)
assert status == '200 OK'
assert phid in body
placeholder_id = pc.placeholder.id
uri = f'/workbench/erasure_host/{placeholder_id}/'
body, status = user3.get(uri)
assert status == '200 OK'
assert phid not in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_filter_hdd_in_kangaroo(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
csrf = generate_csrf()
uri = f'/inventory/device/?filter=All+DataStorage&csrf_token={csrf}'
body, status = user3.get(uri)
assert status == '200 OK'
for hdd in Device.query.filter_by(type='HardDrive').all():
assert hdd.dhid not in body
user3.get('/workbench/')
pc = Device.query.filter_by(type='Laptop').first()
data = {
'csrf_token': generate_csrf(),
'phid': pc.phid(),
}
user3.post('/workbench/', data=data)
csrf = generate_csrf()
uri = f'/inventory/device/?filter=All+DataStorage&csrf_token={csrf}'
body, status = user3.get(uri)
assert status == '200 OK'
for hdd in Device.query.filter_by(type='HardDrive').all():
assert hdd.dhid in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_snapshot_is_server_erase(user3: UserClientFlask):
snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
user3.get('/workbench/')
data = {
'csrf_token': generate_csrf(),
'phid': snapshot.device.phid(),
}
user3.post('/workbench/', data=data)
uri = '/inventory/upload-snapshot/'
file_name = 'real-eee-1001pxd.snapshot.12'
snapshot_json = conftest.yaml2json(file_name)
snapshot_json['uuid'] = 'c058e8d2-fb92-47cb-a4b7-522b75561136'
b_snapshot = bytes(json.dumps(snapshot_json), 'utf-8')
file_snap = (BytesIO(b_snapshot), file_name)
user3.get(uri)
data = {
'snapshot': file_snap,
'csrf_token': generate_csrf(),
}
user3.post(uri, data=data, content_type="multipart/form-data")
snapshot2 = Snapshot.query.filter_by(uuid=snapshot_json['uuid']).one()
assert not snapshot.is_server_erase
assert snapshot2.is_server_erase
assert snapshot in snapshot.device.actions
assert snapshot2 in snapshot.device.actions