Merge branch 'testing' into feature/27-permission-posting-action

This commit is contained in:
Jordi Nadeu 2020-07-20 15:42:11 +02:00 committed by GitHub
commit 8fd2e06f0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 2065 additions and 392 deletions

View File

@ -63,5 +63,5 @@ jobs:
- name: Run Tests - name: Run Tests
run: | run: |
pytest --maxfail=5 tests/ pytest -m mvp --maxfail=5 tests/

View File

@ -21,13 +21,10 @@ The requirements are:
`dependencies <http://weasyprint.readthedocs.io/en/stable/install.html>`__. `dependencies <http://weasyprint.readthedocs.io/en/stable/install.html>`__.
Install Devicehub with *pip*: Install Devicehub with *pip*:
``pip3 install ereuse-devicehub -U --pre``. ``pip3 install -U -r requirements.txt -e .``.
Running Running
******* *******
Download, or copy the contents, of `this file <examples/app.py>`__, and
call the new file ``app.py``.
Create a PostgreSQL database called *devicehub* by running Create a PostgreSQL database called *devicehub* by running
`create-db <examples/create-db.sh>`__: `create-db <examples/create-db.sh>`__:
@ -40,28 +37,18 @@ Create a PostgreSQL database called *devicehub* by running
- In MacOS: ``bash examples/create-db.sh devicehub dhub``, and password - In MacOS: ``bash examples/create-db.sh devicehub dhub``, and password
``ereuse``. ``ereuse``.
Create the tables in the database by executing in the same directory Using the `dh` tool for set up with one or multiple inventories.
where ``app.py`` is: Create the tables in the database by executing:
.. code:: bash .. code:: bash
$ flask init-db $ export dhi=dbtest; dh inv add --common --name dbtest
Finally, run the app: Finally, run the app:
.. code:: bash .. code:: bash
$ flask run $ export dhi=dbtest;dh run --debugger
The error ``flask: command not found`` can happen when you are not in a
*virtual environment*. Try executing then ``python3 -m flask``.
Execute ``flask`` only to know all the administration options Devicehub
offers.
See the `Flask
quickstart <http://flask.pocoo.org/docs/1.0/quickstart/>`__ for more
info.
The error bdist_wheel can happen when you work with a *virtual environment*. The error bdist_wheel can happen when you work with a *virtual environment*.
To fix it, install in the *virtual environment* wheel To fix it, install in the *virtual environment* wheel
@ -70,9 +57,14 @@ package. ``pip3 install wheel``
Multiple instances Multiple instances
------------------ ------------------
Devicehub can run as a single inventory or with multiple inventories, Devicehub can run as a single inventory or with multiple inventories,
each inventory being an instance of the ``devicehub``. To execute each inventory being an instance of the ``devicehub``. To add a new inventory
one instance, use the ``flask`` command, to execute multiple instances execute:
use the ``dh`` command. The ``dh`` command is like ``flask``, but
.. code:: bash
$ export dhi=dbtest; dh inv add --name dbtest
Note: The ``dh`` command is like ``flask``, but
it allows you to create and delete instances, and interface to them it allows you to create and delete instances, and interface to them
directly. directly.
@ -86,6 +78,68 @@ Testing
password ``ereuse``. password ``ereuse``.
3. Execute at the root folder of the project ``python3 setup.py test``. 3. Execute at the root folder of the project ``python3 setup.py test``.
Migrations
**********
At this stage, migration files are created manually.
Set up the database:
.. code:: bash
$ sudo su - postgres
$ bash $PATH_TO_DEVIHUBTEAL/examples/create-db.sh devicehub dhub
Initialize the database:
.. code:: bash
$ export dhi=dbtest; dh inv add --common --name dbtest
This command will create the schemas, tables in the specified database.
Then we need to stamp the initial migration.
.. code:: bash
$ alembic stamp head
This command will set the revision **fbb7e2a0cde0_initial** as our initial migration.
For more info in migration stamping please see https://alembic.sqlalchemy.org/en/latest/cookbook.html
Whenever a change needed eg to create a new schema, alter an existing table, column or perform any
operation on tables, create a new revision file:
.. code:: bash
$ alembic revision -m "A table change"
This command will create a new revision file with name `<revision_id>_a_table_change`.
Edit the generated file with the necessary operations to perform the migration:
.. code:: bash
$ alembic edit <revision_id>
Apply migrations using:
.. code:: bash
$ alembic -x inventory=dbtest upgrade head
Then to go back to previous db version:
.. code:: bash
$ alembic -x inventory=dbtest downgrade <revision_id>
To see a full list of migrations use
.. code:: bash
$ alembic history
Generating the docs Generating the docs
******************* *******************

74
alembic.ini Normal file
View File

@ -0,0 +1,74 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = ereuse_devicehub/migrations
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@ -0,0 +1,74 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = migrations
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@ -10,6 +10,7 @@ from ereuse_utils.session import DevicehubClient
from flask.globals import _app_ctx_stack, g from flask.globals import _app_ctx_stack, g
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from teal.teal import Teal from teal.teal import Teal
from teal.db import SchemaSQLAlchemy
from ereuse_devicehub.auth import Auth from ereuse_devicehub.auth import Auth
from ereuse_devicehub.client import Client from ereuse_devicehub.client import Client
@ -115,6 +116,16 @@ class Devicehub(Teal):
self.db.session.commit() self.db.session.commit()
print('done.') print('done.')
def _init_db(self, exclude_schema=None) -> bool:
if exclude_schema:
assert isinstance(self.db, SchemaSQLAlchemy)
self.db.create_all(exclude_schema=exclude_schema)
else:
self.db.create_all()
return True
@click.confirmation_option(prompt='Are you sure you want to delete the inventory {}?' @click.confirmation_option(prompt='Are you sure you want to delete the inventory {}?'
.format(os.environ.get('dhi'))) .format(os.environ.get('dhi')))
def delete_inventory(self): def delete_inventory(self):

View File

@ -77,10 +77,12 @@ class Dummy:
runner.invoke('tag', 'add', id, runner.invoke('tag', 'add', id,
'-p', 'https://t.devicetag.io', '-p', 'https://t.devicetag.io',
'-s', sec, '-s', sec,
'-u', user1.user["id"],
'-o', org_id) '-o', org_id)
# create tag for pc-laudem # create tag for pc-laudem
runner.invoke('tag', 'add', 'tagA', runner.invoke('tag', 'add', 'tagA',
'-p', 'https://t.devicetag.io', '-p', 'https://t.devicetag.io',
'-u', user1.user["id"],
'-s', 'tagA-secondary') '-s', 'tagA-secondary')
files = tuple(Path(__file__).parent.joinpath('files').iterdir()) files = tuple(Path(__file__).parent.joinpath('files').iterdir())
print('done.') print('done.')
@ -144,7 +146,7 @@ class Dummy:
res=Lot, res=Lot,
item='{}/devices'.format(lot_user3['id']), item='{}/devices'.format(lot_user3['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 11, 14)]) query=[('id', pc) for pc in itertools.islice(pcs, 11, 14)])
lot4, _ = user4.post({}, lot4, _ = user4.post({},
res=Lot, res=Lot,
item='{}/devices'.format(lot_user4['id']), item='{}/devices'.format(lot_user4['id']),

View File

@ -20,9 +20,6 @@ device:
- type: Tag - type: Tag
id: tag1 id: tag1
actions: actions:
- type: VisualTest
appearanceRange: A
functionalityRange: B
- type: BenchmarkRamSysbench - type: BenchmarkRamSysbench
rate: 2444 rate: 2444
elapsed: 1 elapsed: 1

View File

@ -0,0 +1 @@
Generic single-database configuration.

View File

@ -0,0 +1,88 @@
from __future__ import with_statement
import os
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from alembic import context
from ereuse_devicehub.config import DevicehubConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.models import Thing
target_metadata = Thing.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_url():
# url = os.environ["DATABASE_URL"]
url = DevicehubConfig.SQLALCHEMY_DATABASE_URI
return url
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = get_url()
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# connectable = engine_from_config(
# config.get_section(config.config_ini_section),
# prefix="sqlalchemy.",
# poolclass=pool.NullPool,
# )
url = get_url()
connectable = create_engine(url)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -0,0 +1,33 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import citext
import teal
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
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():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,41 @@
"""Owner in tags
Revision ID: b9b0ee7d9dca
Revises: 151253ac5c55
Create Date: 2020-06-30 17:41:28.611314
"""
from alembic import op
from alembic import context
import sqlalchemy as sa
import sqlalchemy_utils
from sqlalchemy.dialects import postgresql
import citext
import teal
# revision identifiers, used by Alembic.
revision = 'b9b0ee7d9dca'
down_revision = 'fbb7e2a0cde0'
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('tag', sa.Column('owner_id', postgresql.UUID(), nullable=True), schema=f'{get_inv()}')
op.create_foreign_key("fk_tag_owner_id_user_id",
"tag", "user",
["owner_id"], ["id"],
ondelete="SET NULL",
source_schema=f'{get_inv()}', referent_schema='common')
def downgrade():
op.drop_constraint("fk_tag_owner_id_user_id", "tag", type_="foreignkey", schema=f'{get_inv()}')
op.drop_column('tag', 'owner_id', schema=f'{get_inv()}')

File diff suppressed because one or more lines are too long

View File

@ -267,6 +267,7 @@ class MigrateFromDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.MigrateFrom SCHEMA = schemas.MigrateFrom
class TransferredDef(ActionDef): class TransferredDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.Transferred SCHEMA = schemas.Transferred

View File

@ -1423,6 +1423,7 @@ class DisposeProduct(Trade):
# performing :class:`.ToDispose` + :class:`.Receive` to a # performing :class:`.ToDispose` + :class:`.Receive` to a
# ``RecyclingCenter``. # ``RecyclingCenter``.
class TransferOwnershipBlockchain(Trade): class TransferOwnershipBlockchain(Trade):
""" The act of change owenership of devices between two users (ethereum address)""" """ The act of change owenership of devices between two users (ethereum address)"""
@ -1551,6 +1552,7 @@ def update_parent(target: Union[EraseBasic, Test, Install], device: Device, _, _
class InvalidRangeForPrice(ValueError): class InvalidRangeForPrice(ValueError):
pass pass
class Transferred(ActionWithMultipleDevices): class Transferred(ActionWithMultipleDevices):
"""Transferred through blockchain.""" """Transferred through blockchain."""
pass pass

View File

@ -12,7 +12,7 @@ from ereuse_devicehub.resources.action.models import Action, RateComputer, Snaps
InitTransfer InitTransfer
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
from ereuse_devicehub.resources.device.models import Component, Computer from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import SnapshotSoftware from ereuse_devicehub.resources.enums import SnapshotSoftware, Severity
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
@ -106,8 +106,10 @@ class ActionView(View):
if price: if price:
snapshot.actions.add(price) snapshot.actions.add(price)
elif snapshot.software == SnapshotSoftware.WorkbenchAndroid: elif snapshot.software == SnapshotSoftware.WorkbenchAndroid:
pass # TODO try except to compute RateMobile pass # TODO try except to compute RateMobile
# Check if HID is null and add Severity:Warning to Snapshot
if snapshot.device.hid is None:
snapshot.severity = Severity.Warning
db.session.add(snapshot) db.session.add(snapshot)
db.session().final_flush() db.session().final_flush()
ret = self.schema.jsonify(snapshot) # transform it back ret = self.schema.jsonify(snapshot) # transform it back

View File

@ -4,7 +4,7 @@ from teal.resource import Converters, Resource
from ereuse_devicehub.resources.device import schemas from ereuse_devicehub.resources.device import schemas
from ereuse_devicehub.resources.device.models import Manufacturer from ereuse_devicehub.resources.device.models import Manufacturer
from ereuse_devicehub.resources.device.views import DeviceView, ManufacturerView from ereuse_devicehub.resources.device.views import DeviceView, DeviceMergeView, ManufacturerView
class DeviceDef(Resource): class DeviceDef(Resource):
@ -26,6 +26,13 @@ class DeviceDef(Resource):
super().__init__(app, import_name, static_folder, static_url_path, template_folder, super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands) url_prefix, subdomain, url_defaults, root_path, cli_commands)
device_merge = DeviceMergeView.as_view('merge-devices', definition=self, auth=app.auth)
if self.AUTH:
device_merge = app.auth.requires_auth(device_merge)
self.add_url_rule('/<{}:{}>/merge/'.format(self.ID_CONVERTER.value, self.ID_NAME),
view_func=device_merge,
methods={'POST'})
class ComputerDef(DeviceDef): class ComputerDef(DeviceDef):
VIEW = None VIEW = None

View File

@ -52,7 +52,7 @@ class Device(Thing):
""" """
type = Column(Unicode(STR_SM_SIZE), nullable=False) type = Column(Unicode(STR_SM_SIZE), nullable=False)
hid = Column(Unicode(), check_lower('hid'), unique=False) hid = Column(Unicode(), check_lower('hid'), unique=False)
hid.comment = """The Hardware ID (HID) is the unique ID traceability hid.comment = """The Hardware ID (HID) is the ID traceability
systems use to ID a device globally. This field is auto-generated systems use to ID a device globally. This field is auto-generated
from Devicehub using literal identifiers from the device, from Devicehub using literal identifiers from the device,
so it can re-generated *offline*. so it can re-generated *offline*.

View File

@ -12,7 +12,6 @@ from teal.marshmallow import ValidationError
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action.models import Remove from ereuse_devicehub.resources.action.models import Remove
from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.models import Component, Computer, Device from ereuse_devicehub.resources.device.models import Component, Computer, Device
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
@ -151,9 +150,6 @@ class Sync:
""" """
assert inspect(device).transient, 'Device cannot be already synced from DB' assert inspect(device).transient, 'Device cannot be already synced from DB'
assert all(inspect(tag).transient for tag in device.tags), 'Tags cannot be synced from DB' assert all(inspect(tag).transient for tag in device.tags), 'Tags cannot be synced from DB'
if not device.tags and not device.hid:
# We cannot identify this device
raise NeedsId()
db_device = None db_device = None
if device.hid: if device.hid:
with suppress(ResourceNotFound): with suppress(ResourceNotFound):

View File

@ -1,10 +1,13 @@
import datetime import datetime
import uuid
from itertools import filterfalse
import marshmallow import marshmallow
from flask import current_app as app, render_template, request, Response from flask import current_app as app, render_template, request, Response
from flask.json import jsonify from flask.json import jsonify
from flask_sqlalchemy import Pagination from flask_sqlalchemy import Pagination
from marshmallow import fields, fields as f, validate as v, ValidationError from marshmallow import fields, fields as f, validate as v, ValidationError, \
Schema as MarshmallowSchema
from teal import query from teal import query
from teal.cache import cache from teal.cache import cache
from teal.resource import View from teal.resource import View
@ -19,6 +22,7 @@ from ereuse_devicehub.resources.device.models import Device, Manufacturer, Compu
from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.device.search import DeviceSearch
from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.lot.models import LotDeviceDescendants
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.enums import SnapshotSoftware
class OfType(f.Str): class OfType(f.Str):
@ -152,6 +156,67 @@ class DeviceView(View):
return query.filter(*args['filter']).order_by(*args['sort']) return query.filter(*args['filter']).order_by(*args['sort'])
class DeviceMergeView(View):
"""View for merging two devices
Ex. ``device/<id>/merge/id=X``.
"""
class FindArgs(MarshmallowSchema):
id = fields.Integer()
def get_merge_id(self) -> uuid.UUID:
args = self.QUERY_PARSER.parse(self.find_args, request, locations=('querystring',))
return args['id']
def post(self, id: uuid.UUID):
device = Device.query.filter_by(id=id).one()
with_device = Device.query.filter_by(id=self.get_merge_id()).one()
self.merge_devices(device, with_device)
db.session().final_flush()
ret = self.schema.jsonify(device)
ret.status_code = 201
db.session.commit()
return ret
def merge_devices(self, base_device, with_device):
"""Merge the current device with `with_device` by
adding all `with_device` actions under the current device.
This operation is highly costly as it forces refreshing
many models in session.
"""
snapshots = sorted(filterfalse(lambda x: not isinstance(x, actions.Snapshot), (base_device.actions + with_device.actions)))
workbench_snapshots = [ s for s in snapshots if s.software == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid)]
latest_snapshot_device = [ d for d in (base_device, with_device) if d.id == snapshots[-1].device.id][0]
latest_snapshotworkbench_device = [ d for d in (base_device, with_device) if d.id == workbench_snapshots[-1].device.id][0]
# Adding actions of with_device
with_actions_one = [a for a in with_device.actions if isinstance(a, actions.ActionWithOneDevice)]
with_actions_multiple = [a for a in with_device.actions if isinstance(a, actions.ActionWithMultipleDevices)]
for action in with_actions_one:
if action.parent:
action.parent = base_device
else:
base_device.actions_one.add(action)
for action in with_actions_multiple:
if action.parent:
action.parent = base_device
else:
base_device.actions_multiple.add(action)
# Keeping the components of latest SnapshotWorkbench
base_device.components = latest_snapshotworkbench_device.components
# Properties from latest Snapshot
base_device.type = latest_snapshot_device.type
base_device.hid = latest_snapshot_device.hid
base_device.manufacturer = latest_snapshot_device.manufacturer
base_device.model = latest_snapshot_device.model
base_device.chassis = latest_snapshot_device.chassis
class ManufacturerView(View): class ManufacturerView(View):
class FindArgs(marshmallow.Schema): class FindArgs(marshmallow.Schema):
search = marshmallow.fields.Str(required=True, search = marshmallow.fields.Str(required=True,

View File

@ -43,7 +43,10 @@ class DeviceRow(OrderedDict):
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
except: except:
self['Trading state'] = '' self['Trading state'] = ''
self['Price'] = device.price.price or '' try:
self['Price'] = device.price
except:
self['Price'] = ''
if isinstance(device, d.Computer): if isinstance(device, d.Computer):
self['Processor'] = device.processor_model self['Processor'] = device.processor_model
self['RAM (MB)'] = device.ram_size self['RAM (MB)'] = device.ram_size
@ -73,7 +76,7 @@ class DeviceRow(OrderedDict):
# todo put an input specific order (non alphabetic) & where are a list of types components # todo put an input specific order (non alphabetic) & where are a list of types components
for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str
max = self.NUMS.get(type, 4) max = self.NUMS.get(type, 4)
if type not in ['Component', 'HardDrive', 'SolidStateDrive', 'Camera', 'Battery']: if type not in ['Component', 'HardDrive', 'SolidStateDrive']:
i = 1 i = 1
for component in (r for r in self.device.components if r.type == type): for component in (r for r in self.device.components if r.type == type):
self.fill_component(type, i, component) self.fill_component(type, i, component)

View File

@ -18,6 +18,7 @@ class TagDef(Resource):
VIEW = TagView VIEW = TagView
ID_CONVERTER = Converters.lower ID_CONVERTER = Converters.lower
OWNER_H = 'The id of the user who owns this tag. '
ORG_H = 'The name of an existing organization in the DB. ' ORG_H = 'The name of an existing organization in the DB. '
'By default the organization operating this Devicehub.' 'By default the organization operating this Devicehub.'
PROV_H = 'The Base URL of the provider; scheme + domain. Ex: "https://foo.com". ' PROV_H = 'The Base URL of the provider; scheme + domain. Ex: "https://foo.com". '
@ -48,6 +49,7 @@ class TagDef(Resource):
view_func=device_view, view_func=device_view,
methods={'PUT'}) methods={'PUT'})
@option('-u', '--owner', help=OWNER_H)
@option('-o', '--org', help=ORG_H) @option('-o', '--org', help=ORG_H)
@option('-p', '--provider', help=PROV_H) @option('-p', '--provider', help=PROV_H)
@option('-s', '--sec', help=Tag.secondary.comment) @option('-s', '--sec', help=Tag.secondary.comment)
@ -55,18 +57,19 @@ class TagDef(Resource):
def create_tag(self, def create_tag(self,
id: str, id: str,
org: str = None, org: str = None,
owner: str = None,
sec: str = None, sec: str = None,
provider: str = None): provider: str = None):
"""Create a tag with the given ID.""" """Create a tag with the given ID."""
db.session.add(Tag(**self.schema.load( db.session.add(Tag(**self.schema.load(
dict(id=id, org=org, secondary=sec, provider=provider) dict(id=id, owner=owner, org=org, secondary=sec, provider=provider)
))) )))
db.session.commit() db.session.commit()
@option('--org', help=ORG_H) @option('--org', help=ORG_H)
@option('--provider', help=PROV_H) @option('--provider', help=PROV_H)
@argument('path', type=cli.Path(writable=True)) @argument('path', type=cli.Path(writable=True))
def create_tags_csv(self, path: pathlib.Path, org: str, provider: str): def create_tags_csv(self, path: pathlib.Path, owner: str, org: str, provider: str):
"""Creates tags by reading CSV from ereuse-tag. """Creates tags by reading CSV from ereuse-tag.
CSV must have the following columns: CSV must have the following columns:
@ -77,6 +80,6 @@ class TagDef(Resource):
with path.open() as f: with path.open() as f:
for id, sec in csv.reader(f): for id, sec in csv.reader(f):
db.session.add(Tag(**self.schema.load( db.session.add(Tag(**self.schema.load(
dict(id=id, org=org, secondary=sec, provider=provider) dict(id=id, owner=owner, org=org, secondary=sec, provider=provider)
))) )))
db.session.commit() db.session.commit()

View File

@ -1,6 +1,7 @@
from contextlib import suppress from contextlib import suppress
from typing import Set from typing import Set
from flask import g
from boltons import urlutils from boltons import urlutils
from sqlalchemy import BigInteger, Column, ForeignKey, UniqueConstraint from sqlalchemy import BigInteger, Column, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
@ -12,6 +13,7 @@ from teal.resource import url_for_resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.agent.models import Organization from ereuse_devicehub.resources.agent.models import Organization
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.user.models import User
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
@ -26,6 +28,11 @@ class Tags(Set['Tag']):
class Tag(Thing): class Tag(Thing):
id = Column(db.CIText(), primary_key=True) id = Column(db.CIText(), primary_key=True)
id.comment = """The ID of the tag.""" id.comment = """The ID of the tag."""
owner_id = Column(UUID(as_uuid=True),
ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
owner = relationship(User, primaryjoin=owner_id == User.id)
org_id = Column(UUID(as_uuid=True), org_id = Column(UUID(as_uuid=True),
ForeignKey(Organization.id), ForeignKey(Organization.id),
primary_key=True, primary_key=True,
@ -50,7 +57,7 @@ class Tag(Thing):
primaryjoin=Device.id == device_id) primaryjoin=Device.id == device_id)
"""The device linked to this tag.""" """The device linked to this tag."""
secondary = Column(db.CIText(), index=True) secondary = Column(db.CIText(), index=True)
secondary.comment = """A secondary identifier for this tag. secondary.comment = """A secondary identifier for this tag.
It has the same constraints as the main one. Only needed in special cases. It has the same constraints as the main one. Only needed in special cases.
""" """

View File

@ -3,6 +3,7 @@ from sqlalchemy.util import OrderedSet
from teal.marshmallow import SanitizedStr, URL from teal.marshmallow import SanitizedStr, URL
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.user.schemas import User
from ereuse_devicehub.resources.agent.schemas import Organization from ereuse_devicehub.resources.agent.schemas import Organization
from ereuse_devicehub.resources.device.schemas import Device from ereuse_devicehub.resources.device.schemas import Device
from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.schemas import Thing
@ -22,6 +23,7 @@ class Tag(Thing):
provider = URL(description=m.Tag.provider.comment, provider = URL(description=m.Tag.provider.comment,
validator=without_slash) validator=without_slash)
device = NestedOn(Device, dump_only=True) device = NestedOn(Device, dump_only=True)
owner = NestedOn(User, only_query='id')
org = NestedOn(Organization, collection_class=OrderedSet, only_query='id') org = NestedOn(Organization, collection_class=OrderedSet, only_query='id')
secondary = SanitizedStr(lower=True, description=m.Tag.secondary.comment) secondary = SanitizedStr(lower=True, description=m.Tag.secondary.comment)
printable = Boolean(dump_only=True, decsription=m.Tag.printable.__doc__) printable = Boolean(dump_only=True, decsription=m.Tag.printable.__doc__)

View File

@ -4,12 +4,14 @@ from teal.marshmallow import ValidationError
from teal.resource import View, url_for_resource from teal.resource import View, url_for_resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub import auth
from ereuse_devicehub.query import things_response from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.tag import Tag from ereuse_devicehub.resources.tag import Tag
class TagView(View): class TagView(View):
@auth.Auth.requires_auth
def post(self): def post(self):
"""Creates a tag.""" """Creates a tag."""
num = request.args.get('num', type=int) num = request.args.get('num', type=int)
@ -19,8 +21,10 @@ class TagView(View):
res = self._post_one() res = self._post_one()
return res return res
@auth.Auth.requires_auth
def find(self, args: dict): def find(self, args: dict):
tags = Tag.query.filter(Tag.is_printable_q()) \ tags = Tag.query.filter(Tag.is_printable_q()) \
.filter_by(owner=g.user) \
.order_by(Tag.created.desc()) \ .order_by(Tag.created.desc()) \
.paginate(per_page=200) # type: Pagination .paginate(per_page=200) # type: Pagination
return things_response( return things_response(

View File

@ -1,3 +1,4 @@
alembic==1.4.2
anytree==2.4.3 anytree==2.4.3
apispec==0.39.0 apispec==0.39.0
boltons==18.0.1 boltons==18.0.1

View File

@ -1,2 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price,Processor,RAM (GB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,Battery 1,Battery 1 Manufacturer,Battery 1 Model,Battery 1 Serial Number,Battery 2,Battery 2 Manufacturer,Battery 2 Model,Battery 2 Serial Number,Battery 3,Battery 3 Manufacturer,Battery 3 Model,Battery 3 Serial Number,Battery 4,Battery 4 Manufacturer,Battery 4 Model,Battery 4 Serial Number,Camera 1,Camera 1 Manufacturer,Camera 1 Model,Camera 1 Serial Number,Camera 2,Camera 2 Manufacturer,Camera 2 Model,Camera 2 Serial Number,Camera 3,Camera 3 Manufacturer,Camera 3 Model,Camera 3 Serial Number,Camera 4,Camera 4 Manufacturer,Camera 4 Model,Camera 4 Serial Number,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price,Processor,RAM (MB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,Battery 1,Battery 1 Manufacturer,Battery 1 Model,Battery 1 Serial Number,Battery 2,Battery 2 Manufacturer,Battery 2 Model,Battery 2 Serial Number,Battery 3,Battery 3 Manufacturer,Battery 3 Model,Battery 3 Serial Number,Battery 4,Battery 4 Manufacturer,Battery 4 Model,Battery 4 Serial Number,Camera 1,Camera 1 Manufacturer,Camera 1 Model,Camera 1 Serial Number,Camera 2,Camera 2 Manufacturer,Camera 2 Model,Camera 2 Serial Number,Camera 3,Camera 3 Manufacturer,Camera 3 Model,Camera 3 Serial Number,Camera 4,Camera 4 Manufacturer,Camera 4 Model,Camera 4 Serial Number,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number
Desktop,Microtower,,,,d1s,d1ml,d1mr,Tue Jul 2 10:35:10 2019,,p1ml,0,0,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 2: model gc1ml, S/N gc1s",gc1s,gc1s,gc1s,,,,,,,,,,,,,,,,,,"Processor 4: model p1ml, S/N p1s",p1s,p1s,p1s,,1.6,,,,,"RamModule 3: model rm1ml, S/N rm1s",rm1s,rm1s,rm1s,,1333,,,,,,,,,,,,,,,,,,,, Desktop,Microtower,,,,d1s,d1ml,d1mr,Tue Jul 2 10:35:10 2019,,,,p1ml,0,0,1.0,Very low,1.0,Very low,1.0,Very low,1.0,Very low,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 2: model gc1ml, S/N gc1s",gc1s,gc1s,gc1s,,,,,,,,,,,,,,,,,,"Processor 4: model p1ml, S/N p1s",p1s,p1s,p1s,,1.6,,,,,"RamModule 3: model rm1ml, S/N rm1s",rm1s,rm1s,rm1s,,1333,,,,,,,,,,,,,,,,,,,,

1 Type Chassis Tag 1 Tag 2 Tag 3 Serial Number Model Manufacturer Registered in Physical state Trading state RAM (GB) Price Processor RAM (MB) Data Storage Size (MB) Rate Range Processor Rate Processor Range RAM Rate RAM Range Data Storage Rate Data Storage Range Battery 1 Battery 1 Manufacturer Battery 1 Model Battery 1 Serial Number Battery 2 Battery 2 Manufacturer Battery 2 Model Battery 2 Serial Number Battery 3 Battery 3 Manufacturer Battery 3 Model Battery 3 Serial Number Battery 4 Battery 4 Manufacturer Battery 4 Model Battery 4 Serial Number Camera 1 Camera 1 Manufacturer Camera 1 Model Camera 1 Serial Number Camera 2 Camera 2 Manufacturer Camera 2 Model Camera 2 Serial Number Camera 3 Camera 3 Manufacturer Camera 3 Model Camera 3 Serial Number Camera 4 Camera 4 Manufacturer Camera 4 Model Camera 4 Serial Number DataStorage 1 DataStorage 1 Manufacturer DataStorage 1 Model DataStorage 1 Serial Number DataStorage 2 DataStorage 2 Manufacturer DataStorage 2 Model DataStorage 2 Serial Number DataStorage 3 DataStorage 3 Manufacturer DataStorage 3 Model DataStorage 3 Serial Number DataStorage 4 DataStorage 4 Manufacturer DataStorage 4 Model DataStorage 4 Serial Number Display 1 Display 1 Manufacturer Display 1 Model Display 1 Serial Number GraphicCard 1 GraphicCard 1 Manufacturer GraphicCard 1 Model GraphicCard 1 Serial Number GraphicCard 1 Memory (MB) GraphicCard 2 GraphicCard 2 Manufacturer GraphicCard 2 Model GraphicCard 2 Serial Number Motherboard 1 Motherboard 1 Manufacturer Motherboard 1 Model Motherboard 1 Serial Number NetworkAdapter 1 NetworkAdapter 1 Manufacturer NetworkAdapter 1 Model NetworkAdapter 1 Serial Number NetworkAdapter 2 NetworkAdapter 2 Manufacturer NetworkAdapter 2 Model NetworkAdapter 2 Serial Number Processor 1 Processor 1 Manufacturer Processor 1 Model Processor 1 Serial Number Processor 1 Number of cores Processor 1 Speed (GHz) Processor 2 Processor 2 Manufacturer Processor 2 Model Processor 2 Serial Number RamModule 1 RamModule 1 Manufacturer RamModule 1 Model RamModule 1 Serial Number RamModule 1 Size (MB) RamModule 1 Speed (MHz) RamModule 2 RamModule 2 Manufacturer RamModule 2 Model RamModule 2 Serial Number RamModule 3 RamModule 3 Manufacturer RamModule 3 Model RamModule 3 Serial Number RamModule 4 RamModule 4 Manufacturer RamModule 4 Model RamModule 4 Serial Number SoundCard 1 SoundCard 1 Manufacturer SoundCard 1 Model SoundCard 1 Serial Number SoundCard 2 SoundCard 2 Manufacturer SoundCard 2 Model SoundCard 2 Serial Number
2 Desktop Microtower d1s d1ml d1mr Tue Jul 2 10:35:10 2019 0 p1ml 0 0 0.8 1.0 Very low 1.0 Very low 1.0 Very low 1.0 Very low GraphicCard 2: model gc1ml, S/N gc1s gc1s gc1s gc1s Processor 4: model p1ml, S/N p1s p1s p1s p1s 1.6 RamModule 3: model rm1ml, S/N rm1s rm1s rm1s rm1s 1333

View File

@ -1,2 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price
ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Oct 24 20:57:18 2018 ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Oct 24 20:57:18 2018
1 Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price
2 ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Oct 24 20:57:18 2018 ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Oct 24 20:57:18 2018

View File

@ -1,2 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price
Keyboard,,,,,bar,foo,baz,Wed Oct 24 21:01:48 2018 Keyboard,,,,,bar,foo,baz,Wed Oct 24 21:01:48 2018

1 Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price
2 Keyboard,,,,,bar,foo,baz,Wed Oct 24 21:01:48 2018 Keyboard,,,,,bar,foo,baz,Wed Oct 24 21:01:48 2018

View File

@ -1,5 +1,5 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price,Processor,RAM (GB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,Battery 1,Battery 1 Manufacturer,Battery 1 Model,Battery 1 Serial Number,Battery 2,Battery 2 Manufacturer,Battery 2 Model,Battery 2 Serial Number,Battery 3,Battery 3 Manufacturer,Battery 3 Model,Battery 3 Serial Number,Battery 4,Battery 4 Manufacturer,Battery 4 Model,Battery 4 Serial Number,Camera 1,Camera 1 Manufacturer,Camera 1 Model,Camera 1 Serial Number,Camera 2,Camera 2 Manufacturer,Camera 2 Model,Camera 2 Serial Number,Camera 3,Camera 3 Manufacturer,Camera 3 Model,Camera 3 Serial Number,Camera 4,Camera 4 Manufacturer,Camera 4 Model,Camera 4 Serial Number,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price,Processor,RAM (MB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,Battery 1,Battery 1 Manufacturer,Battery 1 Model,Battery 1 Serial Number,Battery 2,Battery 2 Manufacturer,Battery 2 Model,Battery 2 Serial Number,Battery 3,Battery 3 Manufacturer,Battery 3 Model,Battery 3 Serial Number,Battery 4,Battery 4 Manufacturer,Battery 4 Model,Battery 4 Serial Number,Camera 1,Camera 1 Manufacturer,Camera 1 Model,Camera 1 Serial Number,Camera 2,Camera 2 Manufacturer,Camera 2 Model,Camera 2 Serial Number,Camera 3,Camera 3 Manufacturer,Camera 3 Model,Camera 3 Serial Number,Camera 4,Camera 4 Manufacturer,Camera 4 Model,Camera 4 Serial Number,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number
Laptop,Netbook,,,,b8oaas048286,1001pxd,asustek computer inc.,Tue Jul 2 10:38:14 2019,,intel atom cpu n455 @ 1.66ghz,1024,238475,1.98,Very low,1.31,Very low,1.53,Very low,3.76,Medium,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None",,,,256,,,,,"Motherboard 10: model 1001pxd, S/N eee0123456789",eee0123456789,eee0123456789,eee0123456789,"NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8",74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,"NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c",14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,"Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None",,,,1,1.667,,,,,"RamModule 8: model None, S/N None",,,,1024,667,,,,,,,,,,,,,"SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None",,,,"SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001",0x0001,0x0001,0x0001 Laptop,Netbook,,,,b8oaas048286,1001pxd,asustek computer inc.,Tue Jul 2 10:37:44 2019,,,47.40 €,intel atom cpu n455 @ 1.66ghz,1024,238475,1.58,Low,1.31,Low,1.53,Low,3.76,High,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None",,,,256,,,,,"Motherboard 10: model 1001pxd, S/N eee0123456789",eee0123456789,eee0123456789,eee0123456789,"NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8",74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,"NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c",14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,"Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None",,,,1,1.667,,,,,"RamModule 8: model None, S/N None",,,,1024,667,,,,,,,,,,,,,"SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None",,,,"SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001",0x0001,0x0001,0x0001
Desktop,Microtower,,,,d1s,d1ml,d1mr,Tue Jul 2 10:38:14 2019,,p1ml,0,0,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 12: model gc1ml, S/N gc1s",gc1s,gc1s,gc1s,,,,,,,,,,,,,,,,,,"Processor 14: model p1ml, S/N p1s",p1s,p1s,p1s,,1.6,,,,,"RamModule 13: model rm1ml, S/N rm1s",rm1s,rm1s,rm1s,,1333,,,,,,,,,,,,,,,,,,,, Desktop,Microtower,,,,d1s,d1ml,d1mr,Tue Jul 2 10:38:14 2019,,,,p1ml,0,0,1.0,Very low,1.0,Very low,1.0,Very low,1.0,Very low,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 12: model gc1ml, S/N gc1s",gc1s,gc1s,gc1s,,,,,,,,,,,,,,,,,,"Processor 14: model p1ml, S/N p1s",p1s,p1s,p1s,,1.6,,,,,"RamModule 13: model rm1ml, S/N rm1s",rm1s,rm1s,rm1s,,1333,,,,,,,,,,,,,,,,,,,,
Keyboard,,,,,bar,foo,baz,Tue Jul 2 10:38:14 2019, Keyboard,,,,,bar,foo,baz,Tue Jul 2 10:38:14 2019,,,
ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Tue Jul 2 10:38:15 2019, ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Tue Jul 2 10:38:15 2019,,,

Can't render this file because it has a wrong number of fields in line 4.

View File

@ -1,2 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price,Processor,RAM (GB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,Battery 1,Battery 1 Manufacturer,Battery 1 Model,Battery 1 Serial Number,Battery 2,Battery 2 Manufacturer,Battery 2 Model,Battery 2 Serial Number,Battery 3,Battery 3 Manufacturer,Battery 3 Model,Battery 3 Serial Number,Battery 4,Battery 4 Manufacturer,Battery 4 Model,Battery 4 Serial Number,Camera 1,Camera 1 Manufacturer,Camera 1 Model,Camera 1 Serial Number,Camera 2,Camera 2 Manufacturer,Camera 2 Model,Camera 2 Serial Number,Camera 3,Camera 3 Manufacturer,Camera 3 Model,Camera 3 Serial Number,Camera 4,Camera 4 Manufacturer,Camera 4 Model,Camera 4 Serial Number,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price,Processor,RAM (MB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,Battery 1,Battery 1 Manufacturer,Battery 1 Model,Battery 1 Serial Number,Battery 2,Battery 2 Manufacturer,Battery 2 Model,Battery 2 Serial Number,Battery 3,Battery 3 Manufacturer,Battery 3 Model,Battery 3 Serial Number,Battery 4,Battery 4 Manufacturer,Battery 4 Model,Battery 4 Serial Number,Camera 1,Camera 1 Manufacturer,Camera 1 Model,Camera 1 Serial Number,Camera 2,Camera 2 Manufacturer,Camera 2 Model,Camera 2 Serial Number,Camera 3,Camera 3 Manufacturer,Camera 3 Model,Camera 3 Serial Number,Camera 4,Camera 4 Manufacturer,Camera 4 Model,Camera 4 Serial Number,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number
Laptop,Netbook,,,,b8oaas048286,1001pxd,asustek computer inc.,Tue Jul 2 10:37:44 2019,,intel atom cpu n455 @ 1.66ghz,1024,238475,1.98,Very low,1.31,Very low,1.53,Very low,3.76,Medium,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None",,,,256,,,,,"Motherboard 10: model 1001pxd, S/N eee0123456789",eee0123456789,eee0123456789,eee0123456789,"NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8",74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,"NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c",14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,"Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None",,,,1,1.667,,,,,"RamModule 8: model None, S/N None",,,,1024,667,,,,,,,,,,,,,"SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None",,,,"SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001",0x0001,0x0001,0x0001 Laptop,Netbook,,,,b8oaas048286,1001pxd,asustek computer inc.,Tue Jul 2 10:37:44 2019,,,47.40 €,intel atom cpu n455 @ 1.66ghz,1024,238475,1.58,Low,1.31,Low,1.53,Low,3.76,High,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None",,,,256,,,,,"Motherboard 10: model 1001pxd, S/N eee0123456789",eee0123456789,eee0123456789,eee0123456789,"NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8",74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,"NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c",14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,"Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None",,,,1,1.667,,,,,"RamModule 8: model None, S/N None",,,,1024,667,,,,,,,,,,,,,"SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None",,,,"SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001",0x0001,0x0001,0x0001

1 Type Chassis Tag 1 Tag 2 Tag 3 Serial Number Model Manufacturer Registered in Physical state Trading state RAM (GB) Price Processor RAM (MB) Data Storage Size (MB) Rate Range Processor Rate Processor Range RAM Rate RAM Range Data Storage Rate Data Storage Range Battery 1 Battery 1 Manufacturer Battery 1 Model Battery 1 Serial Number Battery 2 Battery 2 Manufacturer Battery 2 Model Battery 2 Serial Number Battery 3 Battery 3 Manufacturer Battery 3 Model Battery 3 Serial Number Battery 4 Battery 4 Manufacturer Battery 4 Model Battery 4 Serial Number Camera 1 Camera 1 Manufacturer Camera 1 Model Camera 1 Serial Number Camera 2 Camera 2 Manufacturer Camera 2 Model Camera 2 Serial Number Camera 3 Camera 3 Manufacturer Camera 3 Model Camera 3 Serial Number Camera 4 Camera 4 Manufacturer Camera 4 Model Camera 4 Serial Number DataStorage 1 DataStorage 1 Manufacturer DataStorage 1 Model DataStorage 1 Serial Number DataStorage 2 DataStorage 2 Manufacturer DataStorage 2 Model DataStorage 2 Serial Number DataStorage 3 DataStorage 3 Manufacturer DataStorage 3 Model DataStorage 3 Serial Number DataStorage 4 DataStorage 4 Manufacturer DataStorage 4 Model DataStorage 4 Serial Number Display 1 Display 1 Manufacturer Display 1 Model Display 1 Serial Number GraphicCard 1 GraphicCard 1 Manufacturer GraphicCard 1 Model GraphicCard 1 Serial Number GraphicCard 1 Memory (MB) GraphicCard 2 GraphicCard 2 Manufacturer GraphicCard 2 Model GraphicCard 2 Serial Number Motherboard 1 Motherboard 1 Manufacturer Motherboard 1 Model Motherboard 1 Serial Number NetworkAdapter 1 NetworkAdapter 1 Manufacturer NetworkAdapter 1 Model NetworkAdapter 1 Serial Number NetworkAdapter 2 NetworkAdapter 2 Manufacturer NetworkAdapter 2 Model NetworkAdapter 2 Serial Number Processor 1 Processor 1 Manufacturer Processor 1 Model Processor 1 Serial Number Processor 1 Number of cores Processor 1 Speed (GHz) Processor 2 Processor 2 Manufacturer Processor 2 Model Processor 2 Serial Number RamModule 1 RamModule 1 Manufacturer RamModule 1 Model RamModule 1 Serial Number RamModule 1 Size (MB) RamModule 1 Speed (MHz) RamModule 2 RamModule 2 Manufacturer RamModule 2 Model RamModule 2 Serial Number RamModule 3 RamModule 3 Manufacturer RamModule 3 Model RamModule 3 Serial Number RamModule 4 RamModule 4 Manufacturer RamModule 4 Model RamModule 4 Serial Number SoundCard 1 SoundCard 1 Manufacturer SoundCard 1 Model SoundCard 1 Serial Number SoundCard 2 SoundCard 2 Manufacturer SoundCard 2 Model SoundCard 2 Serial Number
2 Laptop Netbook b8oaas048286 1001pxd asustek computer inc. Tue Jul 2 10:37:44 2019 1024 47.40 € intel atom cpu n455 @ 1.66ghz 1024 238475 1.98 1.58 Very low Low 1.31 Very low Low 1.53 Very low Low 3.76 Medium High GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None 256 Motherboard 10: model 1001pxd, S/N eee0123456789 eee0123456789 eee0123456789 eee0123456789 NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8 74:2f:68:8b:fd:c8 74:2f:68:8b:fd:c8 74:2f:68:8b:fd:c8 NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c 14:da:e9:42:f6:7c 14:da:e9:42:f6:7c 14:da:e9:42:f6:7c Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None 1 1.667 RamModule 8: model None, S/N None 1024 667 SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001 0x0001 0x0001 0x0001

View File

@ -20,6 +20,7 @@ from tests import conftest
from tests.conftest import create_user, file from tests.conftest import create_user, file
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_author(): def test_author():
"""Checks the default created author. """Checks the default created author.
@ -36,6 +37,7 @@ def test_author():
assert e.author == user assert e.author == user
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_erase_basic(): def test_erase_basic():
erasure = models.EraseBasic( erasure = models.EraseBasic(
@ -54,6 +56,7 @@ def test_erase_basic():
assert not erasure.standards, 'EraseBasic themselves do not have standards' assert not erasure.standards, 'EraseBasic themselves do not have standards'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
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."""
@ -68,6 +71,7 @@ def test_validate_device_data_storage():
) )
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_erase_sectors_steps_erasure_standards_hmg_is5(): def test_erase_sectors_steps_erasure_standards_hmg_is5():
erasure = models.EraseSectors( erasure = models.EraseSectors(
@ -89,6 +93,7 @@ def test_erase_sectors_steps_erasure_standards_hmg_is5():
assert {enums.ErasureStandards.HMG_IS5} == erasure.standards assert {enums.ErasureStandards.HMG_IS5} == erasure.standards
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_test_data_storage_working(): def test_test_data_storage_working():
"""Tests TestDataStorage with the resulting properties in Device.""" """Tests TestDataStorage with the resulting properties in Device."""
@ -121,6 +126,7 @@ def test_test_data_storage_working():
assert hdd.problems == [] assert hdd.problems == []
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_install(): def test_install():
hdd = HardDrive(serial_number='sn') hdd = HardDrive(serial_number='sn')
@ -131,6 +137,7 @@ def test_install():
db.session.commit() db.session.commit()
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_update_components_action_one(): def test_update_components_action_one():
computer = Desktop(serial_number='sn1', computer = Desktop(serial_number='sn1',
@ -159,6 +166,7 @@ def test_update_components_action_one():
assert len(test.components) == 1 assert len(test.components) == 1
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_update_components_action_multiple(): def test_update_components_action_multiple():
computer = Desktop(serial_number='sn1', computer = Desktop(serial_number='sn1',
@ -188,6 +196,7 @@ def test_update_components_action_multiple():
assert ready.components assert ready.components
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_update_parent(): def test_update_parent():
computer = Desktop(serial_number='sn1', computer = Desktop(serial_number='sn1',
@ -208,6 +217,7 @@ def test_update_parent():
assert not benchmark.parent assert not benchmark.parent
@pytest.mark.mvp
@pytest.mark.parametrize('action_model_state', @pytest.mark.parametrize('action_model_state',
(pytest.param(ams, id=ams[0].__class__.__name__) (pytest.param(ams, id=ams[0].__class__.__name__)
for ams in [ for ams in [
@ -230,6 +240,7 @@ def test_generic_action(action_model_state: Tuple[models.Action, states.Trading]
assert device['physical'] == state.name assert device['physical'] == state.name
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_live(): def test_live():
"""Tests inserting a Live into the database and GETting it.""" """Tests inserting a Live into the database and GETting it."""
@ -255,18 +266,7 @@ def test_live():
assert device['physical'] == states.Physical.InUse.name assert device['physical'] == states.Physical.InUse.name
@pytest.mark.xfail(reson='Functionality not developed.') @pytest.mark.mvp
def test_live_geoip():
"""Tests performing a Live action using the GEOIP library."""
@pytest.mark.xfail(reson='Develop reserve')
def test_reserve_and_cancel(user: UserClient):
"""Performs a reservation and then cancels it,
checking the attribute `reservees`.
"""
@pytest.mark.parametrize('action_model_state', @pytest.mark.parametrize('action_model_state',
(pytest.param(ams, id=ams[0].__name__) (pytest.param(ams, id=ams[0].__name__)
for ams in [ for ams in [
@ -296,11 +296,7 @@ def test_trade(action_model_state: Tuple[Type[models.Action], states.Trading], u
assert device['trading'] == state.name assert device['trading'] == state.name
@pytest.mark.xfail(reson='Develop migrate') @pytest.mark.mvp
def test_migrate():
pass
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_price_custom(): def test_price_custom():
computer = Desktop(serial_number='sn1', model='ml1', manufacturer='mr1', computer = Desktop(serial_number='sn1', model='ml1', manufacturer='mr1',
@ -322,6 +318,7 @@ def test_price_custom():
assert c['price']['id'] == p['id'] assert c['price']['id'] == p['id']
@pytest.mark.mvp
def test_price_custom_client(user: UserClient): def test_price_custom_client(user: UserClient):
"""As test_price_custom but creating the price through the API.""" """As test_price_custom but creating the price through the API."""
s = file('basic.snapshot') s = file('basic.snapshot')
@ -339,16 +336,7 @@ def test_price_custom_client(user: UserClient):
assert 25 == device['price']['price'] assert 25 == device['price']['price']
@pytest.mark.xfail(reson='Develop test') @pytest.mark.mvp
def test_ereuse_price():
"""Tests the several ways of creating eReuse Price, emulating
from an AggregateRate and ensuring that the different Range
return correct results.
"""
# important to check Range.low no returning warranty2
# Range.verylow not returning nothing
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_erase_physical(): def test_erase_physical():
erasure = models.ErasePhysical( erasure = models.ErasePhysical(
@ -357,27 +345,3 @@ def test_erase_physical():
) )
db.session.add(erasure) db.session.add(erasure)
db.session.commit() db.session.commit()
@pytest.mark.xfail(reson='develop')
def test_measure_battery():
"""Tests the MeasureBattery."""
# todo jn
@pytest.mark.xfail(reson='develop')
def test_test_camera():
"""Tests the TestCamera."""
# todo jn
@pytest.mark.xfail(reson='develop')
def test_test_keyboard():
"""Tests the TestKeyboard."""
# todo jn
@pytest.mark.xfail(reson='develop')
def test_test_trackpad():
"""Tests the TestTrackpad."""
# todo jn

View File

@ -8,6 +8,7 @@ from ereuse_devicehub.devicehub import Devicehub
from tests.conftest import create_user from tests.conftest import create_user
@pytest.mark.mvp
def test_authenticate_success(app: Devicehub): def test_authenticate_success(app: Devicehub):
"""Checks the authenticate method.""" """Checks the authenticate method."""
with app.app_context(): with app.app_context():
@ -16,6 +17,7 @@ def test_authenticate_success(app: Devicehub):
assert response_user == user assert response_user == user
@pytest.mark.mvp
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():
@ -29,6 +31,7 @@ def test_authenticate_error(app: Devicehub):
app.auth.authenticate(token='this is a wrong uuid') app.auth.authenticate(token='this is a wrong uuid')
@pytest.mark.mvp
def test_auth_view(user: UserClient, client: Client): 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)

View File

@ -1,8 +1,19 @@
import pytest import pytest
from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.client import Client from ereuse_devicehub.client import Client
@pytest.mark.mvp
def test_dummy(_app: Devicehub):
"""Tests the dummy cli command."""
runner = _app.test_cli_runner()
runner.invoke('dummy', '--yes')
with _app.app_context():
_app.db.drop_all()
@pytest.mark.mvp
def test_dependencies(): def test_dependencies():
with pytest.raises(ImportError): with pytest.raises(ImportError):
# Simplejson has a different signature than stdlib json # Simplejson has a different signature than stdlib json
@ -12,6 +23,7 @@ def test_dependencies():
# noinspection PyArgumentList # noinspection PyArgumentList
@pytest.mark.mvp
def test_api_docs(client: Client): def test_api_docs(client: Client):
"""Tests /apidocs correct initialization.""" """Tests /apidocs correct initialization."""
docs, _ = client.get('/apidocs') docs, _ = client.get('/apidocs')

View File

@ -1,9 +1,11 @@
import datetime import datetime
from uuid import UUID from uuid import UUID
import pytest
from teal.db import UniqueViolation from teal.db import UniqueViolation
@pytest.mark.mvp
def test_unique_violation(): def test_unique_violation():
class IntegrityErrorMock: class IntegrityErrorMock:
def __init__(self) -> None: def __init__(self) -> None:

View File

@ -1,5 +1,6 @@
import datetime import datetime
from uuid import UUID from uuid import UUID
from flask import g
import pytest import pytest
from colour import Color from colour import Color
@ -22,14 +23,15 @@ 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 MismatchBetweenTags, MismatchBetweenTagsAndHid, \
Sync Sync
from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech, Severity, \ from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech, Severity, \
SnapshotSoftware 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 from tests.conftest import file
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@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(model='p1mo',
@ -76,6 +78,7 @@ def test_device_problems():
pass pass
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_device_schema(): def test_device_schema():
"""Ensures the user does not upload non-writable or extra fields.""" """Ensures the user does not upload non-writable or extra fields."""
@ -84,7 +87,8 @@ def test_device_schema():
device_s.dump(d.Device(id=1)) device_s.dump(d.Device(id=1))
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_physical_properties(): def test_physical_properties():
c = d.Motherboard(slots=2, c = d.Motherboard(slots=2,
usb=3, usb=3,
@ -118,14 +122,21 @@ def test_physical_properties():
'ram_slots': None 'ram_slots': None
} }
assert pc.physical_properties == { assert pc.physical_properties == {
'model': 'foo', 'chassis': ComputerChassis.Tower,
'deliverynote_address': None,
'deposit': 0,
'ethereum_address': None,
'manufacturer': 'bar', 'manufacturer': 'bar',
'model': 'foo',
'owner_id': pc.owner_id,
'receiver_id': None,
'serial_number': 'foo-bar', 'serial_number': 'foo-bar',
'chassis': ComputerChassis.Tower 'transfer_state': TransferState.Initial
} }
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_component_similar_one(): def test_component_similar_one():
snapshot = conftest.file('pc-components.db') snapshot = conftest.file('pc-components.db')
pc = snapshot['device'] pc = snapshot['device']
@ -147,7 +158,8 @@ def test_component_similar_one():
assert componentA.similar_one(pc, blacklist={componentA.id}) assert componentA.similar_one(pc, blacklist={componentA.id})
@pytest.mark.usefixtures('auth_app_context') @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_add_remove(): def test_add_remove():
# Original state: # Original state:
# pc has c1 and c2 # pc has c1 and c2
@ -178,7 +190,8 @@ def test_add_remove():
assert actions[0].components == OrderedSet([c3]) assert actions[0].components == OrderedSet([c3])
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_run_components_empty(): def test_sync_run_components_empty():
"""Syncs a device that has an empty components list. The system should """Syncs a device that has an empty components list. The system should
remove all the components from the device. remove all the components from the device.
@ -195,7 +208,8 @@ def test_sync_run_components_empty():
assert not pc.components assert not pc.components
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_run_components_none(): def test_sync_run_components_none():
"""Syncs a device that has a None components. The system should """Syncs a device that has a None components. The system should
keep all the components from the device. keep all the components from the device.
@ -212,7 +226,8 @@ def test_sync_run_components_none():
assert db_pc.components == pc.components assert db_pc.components == pc.components
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_desktop_new_desktop_no_tag(): def test_sync_execute_register_desktop_new_desktop_no_tag():
"""Syncs a new d.Desktop with HID and without a tag, creating it.""" """Syncs a new d.Desktop with HID and without a tag, creating it."""
# Case 1: device does not exist on DB # Case 1: device does not exist on DB
@ -221,7 +236,8 @@ def test_sync_execute_register_desktop_new_desktop_no_tag():
assert pc.physical_properties == db_pc.physical_properties assert pc.physical_properties == db_pc.physical_properties
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_desktop_existing_no_tag(): def test_sync_execute_register_desktop_existing_no_tag():
"""Syncs an existing d.Desktop with HID and without a tag.""" """Syncs an existing d.Desktop with HID and without a tag."""
pc = d.Desktop(**conftest.file('pc-components.db')['device']) pc = d.Desktop(**conftest.file('pc-components.db')['device'])
@ -232,9 +248,13 @@ def test_sync_execute_register_desktop_existing_no_tag():
**conftest.file('pc-components.db')['device']) # Create a new transient non-db object **conftest.file('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.deposit = 0
pc.owner_id = db_pc.owner_id
pc.transfer_state = TransferState.Initial
assert pc.physical_properties == db_pc.physical_properties assert pc.physical_properties == db_pc.physical_properties
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_no_hid_no_tag(): def test_sync_execute_register_desktop_no_hid_no_tag():
"""Syncs a d.Desktop without HID and no tag. """Syncs a d.Desktop without HID and no tag.
@ -248,7 +268,8 @@ def test_sync_execute_register_desktop_no_hid_no_tag():
Sync().execute_register(pc) Sync().execute_register(pc)
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_desktop_tag_not_linked(): def test_sync_execute_register_desktop_tag_not_linked():
"""Syncs a new d.Desktop with HID and a non-linked tag. """Syncs a new d.Desktop with HID and a non-linked tag.
@ -266,7 +287,8 @@ def test_sync_execute_register_desktop_tag_not_linked():
assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db' assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db'
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str): def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
"""Validates registering a d.Desktop without HID and a non-linked tag. """Validates registering a d.Desktop without HID and a non-linked tag.
@ -276,6 +298,7 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
""" """
tag = Tag(id=tag_id) tag = Tag(id=tag_id)
pc = d.Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([tag])) pc = d.Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([tag]))
db.session.add(g.user)
returned_pc = Sync().execute_register(pc) returned_pc = Sync().execute_register(pc)
db.session.commit() db.session.commit()
assert returned_pc == pc assert returned_pc == pc
@ -288,6 +311,7 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db' assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_does_not_exist(): def test_sync_execute_register_tag_does_not_exist():
"""Ensures not being able to register if the tag does not exist, """Ensures not being able to register if the tag does not exist,
@ -300,7 +324,8 @@ def test_sync_execute_register_tag_does_not_exist():
Sync().execute_register(pc) Sync().execute_register(pc)
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_tag_linked_same_device(): def test_sync_execute_register_tag_linked_same_device():
"""If the tag is linked to the device, regardless if it has HID, """If the tag is linked to the device, regardless if it has HID,
the system should match the device through the tag. the system should match the device through the tag.
@ -320,7 +345,8 @@ def test_sync_execute_register_tag_linked_same_device():
assert next(iter(db_pc.tags)).id == 'foo' assert next(iter(db_pc.tags)).id == 'foo'
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags(): def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
"""Checks that sync raises an error if finds that at least two passed-in """Checks that sync raises an error if finds that at least two passed-in
tags are not linked to the same device. tags are not linked to the same device.
@ -341,7 +367,8 @@ def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
Sync().execute_register(pc1) Sync().execute_register(pc1)
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_sync_execute_register_mismatch_between_tags_and_hid(): def test_sync_execute_register_mismatch_between_tags_and_hid():
"""Checks that sync raises an error if it finds that the HID does """Checks that sync raises an error if it finds that the HID does
not point at the same device as the tag does. not point at the same device as the tag does.
@ -363,6 +390,8 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
Sync().execute_register(pc1) Sync().execute_register(pc1)
@pytest.mark.mvp
@pytest.mark.xfail(reason='It needs to be fixed.')
def test_get_device(app: Devicehub, user: UserClient): def test_get_device(app: Devicehub, user: UserClient):
"""Checks GETting a d.Desktop with its components.""" """Checks GETting a d.Desktop with its components."""
with app.app_context(): with app.app_context():
@ -398,6 +427,8 @@ def test_get_device(app: Devicehub, user: UserClient):
assert pc['type'] == d.Desktop.t assert pc['type'] == d.Desktop.t
@pytest.mark.mvp
@pytest.mark.xfail(reason='It needs to be fixed.')
def test_get_devices(app: Devicehub, user: UserClient): def test_get_devices(app: Devicehub, user: UserClient):
"""Checks GETting multiple devices.""" """Checks GETting multiple devices."""
with app.app_context(): with app.app_context():
@ -426,7 +457,8 @@ def test_get_devices(app: Devicehub, user: UserClient):
) )
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.mvp
@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(technology=DisplayTech.LCD,
manufacturer='foo', manufacturer='foo',
@ -439,6 +471,7 @@ def test_computer_monitor():
db.session.commit() db.session.commit()
@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'}]}
@ -446,6 +479,7 @@ def test_manufacturer(user: UserClient):
assert r.expires > datetime.datetime.now() assert r.expires > datetime.datetime.now()
@pytest.mark.mvp
@pytest.mark.xfail(reason='Develop functionality') @pytest.mark.xfail(reason='Develop functionality')
def test_manufacturer_enforced(): def test_manufacturer_enforced():
"""Ensures that non-computer devices can submit only """Ensures that non-computer devices can submit only
@ -453,6 +487,7 @@ def test_manufacturer_enforced():
""" """
@pytest.mark.mvp
def test_device_properties_format(app: Devicehub, user: UserClient): def test_device_properties_format(app: Devicehub, user: UserClient):
user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
with app.app_context(): with app.app_context():
@ -475,6 +510,7 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
assert format(hdd, 's') == 'seagate 5SV4TQA6 152 GB' assert format(hdd, 's') == 'seagate 5SV4TQA6 152 GB'
@pytest.mark.mvp
def test_device_public(user: UserClient, client: Client): def test_device_public(user: UserClient, client: Client):
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
html, _ = client.get(res=d.Device, item=s['device']['id'], accept=ANY) html, _ = client.get(res=d.Device, item=s['device']['id'], accept=ANY)
@ -482,6 +518,7 @@ def test_device_public(user: UserClient, client: Client):
assert '00:24:8C:7F:CF:2D 100 Mbps' in html assert '00:24:8C:7F:CF:2D 100 Mbps' in html
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_computer_accessory_model(): def test_computer_accessory_model():
sai = d.SAI() sai = d.SAI()
@ -493,6 +530,7 @@ def test_computer_accessory_model():
db.session.commit() db.session.commit()
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_networking_model(): def test_networking_model():
router = d.Router(speed=1000, wireless=True) router = d.Router(speed=1000, wireless=True)

View File

@ -15,6 +15,7 @@ from tests import conftest
from tests.conftest import file from tests.conftest import file
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_device_filters(): def test_device_filters():
schema = Filters() schema = Filters()
@ -171,6 +172,7 @@ def test_device_query_filter_lots(user: UserClient):
), 'Adding both lots is redundant in this case and we have the 4 elements.' ), 'Adding both lots is redundant in this case and we have the 4 elements.'
@pytest.mark.mvp
def test_device_query(user: UserClient): def test_device_query(user: UserClient):
"""Checks result of inventory.""" """Checks result of inventory."""
user.post(conftest.file('basic.snapshot'), res=Snapshot) user.post(conftest.file('basic.snapshot'), res=Snapshot)
@ -183,6 +185,7 @@ def test_device_query(user: UserClient):
assert not pc['tags'] assert not pc['tags']
@pytest.mark.mvp
def test_device_search_all_devices_token_if_empty(app: Devicehub, user: UserClient): def test_device_search_all_devices_token_if_empty(app: Devicehub, user: UserClient):
"""Ensures DeviceSearch can regenerate itself when the table is empty.""" """Ensures DeviceSearch can regenerate itself when the table is empty."""
user.post(file('basic.snapshot'), res=Snapshot) user.post(file('basic.snapshot'), res=Snapshot)
@ -198,6 +201,7 @@ def test_device_search_all_devices_token_if_empty(app: Devicehub, user: UserClie
assert i['items'] assert i['items']
@pytest.mark.mvp
def test_device_search_regenerate_table(app: DeviceSearch, user: UserClient): def test_device_search_regenerate_table(app: DeviceSearch, user: UserClient):
user.post(file('basic.snapshot'), res=Snapshot) user.post(file('basic.snapshot'), res=Snapshot)
i, _ = user.get(res=Device, query=[('search', 'Desktop')]) i, _ = user.get(res=Device, query=[('search', 'Desktop')])
@ -213,6 +217,7 @@ def test_device_search_regenerate_table(app: DeviceSearch, user: UserClient):
assert i['items'], 'Regenerated re-made the table' assert i['items'], 'Regenerated re-made the table'
@pytest.mark.mvp
def test_device_query_search(user: UserClient): def test_device_query_search(user: UserClient):
# todo improve # todo improve
user.post(file('basic.snapshot'), res=Snapshot) user.post(file('basic.snapshot'), res=Snapshot)
@ -226,6 +231,7 @@ def test_device_query_search(user: UserClient):
assert len(i['items']) == 1 assert len(i['items']) == 1
@pytest.mark.mvp
def test_device_query_search_synonyms_asus(user: UserClient): def test_device_query_search_synonyms_asus(user: UserClient):
user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot) user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
i, _ = user.get(res=Device, query=[('search', 'asustek')]) i, _ = user.get(res=Device, query=[('search', 'asustek')])
@ -234,6 +240,7 @@ def test_device_query_search_synonyms_asus(user: UserClient):
assert 1 == len(i['items']) assert 1 == len(i['items'])
@pytest.mark.mvp
def test_device_query_search_synonyms_intel(user: UserClient): def test_device_query_search_synonyms_intel(user: UserClient):
s = file('real-hp.snapshot.11') s = file('real-hp.snapshot.11')
s['device']['model'] = 'foo' # The model had the word 'HP' in it s['device']['model'] = 'foo' # The model had the word 'HP' in it

View File

@ -11,12 +11,14 @@ def noop():
pass pass
@pytest.mark.mvp
@pytest.fixture() @pytest.fixture()
def dispatcher(app: Devicehub, config: TestConfig) -> PathDispatcher: def dispatcher(app: Devicehub, config: TestConfig) -> PathDispatcher:
PathDispatcher.call = Mock(side_effect=lambda *args: args[0]) PathDispatcher.call = Mock(side_effect=lambda *args: args[0])
return PathDispatcher(config_cls=config) return PathDispatcher(config_cls=config)
@pytest.mark.mvp
def test_dispatcher_default(dispatcher: PathDispatcher): def test_dispatcher_default(dispatcher: PathDispatcher):
"""The dispatcher returns not found for an URL that does not """The dispatcher returns not found for an URL that does not
route to an app. route to an app.
@ -27,6 +29,7 @@ def test_dispatcher_default(dispatcher: PathDispatcher):
assert app == PathDispatcher.NOT_FOUND assert app == PathDispatcher.NOT_FOUND
@pytest.mark.mvp
def test_dispatcher_return_app(dispatcher: PathDispatcher): def test_dispatcher_return_app(dispatcher: PathDispatcher):
"""The dispatcher returns the correct app for the URL.""" """The dispatcher returns the correct app for the URL."""
# Note that the dispatcher does not check if the URL points # Note that the dispatcher does not check if the URL points
@ -38,6 +41,7 @@ def test_dispatcher_return_app(dispatcher: PathDispatcher):
assert app.id == 'test' assert app.id == 'test'
@pytest.mark.mvp
def test_dispatcher_users(dispatcher: PathDispatcher): def test_dispatcher_users(dispatcher: PathDispatcher):
"""Users special endpoint returns an app.""" """Users special endpoint returns an app."""
# For now returns the first app, as all apps # For now returns the first app, as all apps

View File

@ -1,25 +1,31 @@
import pytest
import teal.marshmallow import teal.marshmallow
from ereuse_utils.test import ANY from ereuse_utils.test import ANY
import csv
from datetime import datetime
from io import StringIO
from pathlib import Path
from ereuse_devicehub.client import Client, UserClient from ereuse_devicehub.client import Client, UserClient
from ereuse_devicehub.resources.action import models as e from ereuse_devicehub.resources.action.models import Snapshot
from ereuse_devicehub.resources.documents import documents as docs from ereuse_devicehub.resources.documents import documents
from tests.conftest import file from tests.conftest import file
@pytest.mark.mvp
def test_erasure_certificate_public_one(user: UserClient, client: Client): def test_erasure_certificate_public_one(user: UserClient, client: Client):
"""Public user can get certificate from one device as HTML or PDF.""" """Public user can get certificate from one device as HTML or PDF."""
s = file('erase-sectors.snapshot') s = file('erase-sectors.snapshot')
snapshot, _ = user.post(s, res=e.Snapshot) snapshot, _ = user.post(s, res=Snapshot)
doc, response = client.get(res=docs.DocumentDef.t, doc, response = client.get(res=documents.DocumentDef.t,
item='erasures/{}'.format(snapshot['device']['id']), item='erasures/{}'.format(snapshot['device']['id']),
accept=ANY) accept=ANY)
assert 'html' in response.content_type assert 'html' in response.content_type
assert '<html' in doc assert '<html' in doc
assert '2018' in doc assert '2018' in doc
doc, response = client.get(res=docs.DocumentDef.t, doc, response = client.get(res=documents.DocumentDef.t,
item='erasures/{}'.format(snapshot['device']['id']), item='erasures/{}'.format(snapshot['device']['id']),
query=[('format', 'PDF')], query=[('format', 'PDF')],
accept='application/pdf') accept='application/pdf')
@ -27,7 +33,7 @@ def test_erasure_certificate_public_one(user: UserClient, client: Client):
erasure = next(e for e in snapshot['actions'] if e['type'] == 'EraseSectors') erasure = next(e for e in snapshot['actions'] if e['type'] == 'EraseSectors')
doc, response = client.get(res=docs.DocumentDef.t, doc, response = client.get(res=documents.DocumentDef.t,
item='erasures/{}'.format(erasure['id']), item='erasures/{}'.format(erasure['id']),
accept=ANY) accept=ANY)
assert 'html' in response.content_type assert 'html' in response.content_type
@ -35,14 +41,15 @@ def test_erasure_certificate_public_one(user: UserClient, client: Client):
assert '2018' in doc assert '2018' in doc
@pytest.mark.mvp
def test_erasure_certificate_private_query(user: UserClient): def test_erasure_certificate_private_query(user: UserClient):
"""Logged-in user can get certificates using queries as HTML and """Logged-in user can get certificates using queries as HTML and
PDF. PDF.
""" """
s = file('erase-sectors.snapshot') s = file('erase-sectors.snapshot')
snapshot, response = user.post(s, res=e.Snapshot) snapshot, response = user.post(s, res=Snapshot)
doc, response = user.get(res=docs.DocumentDef.t, doc, response = user.get(res=documents.DocumentDef.t,
item='erasures/', item='erasures/',
query=[('filter', {'id': [snapshot['device']['id']]})], query=[('filter', {'id': [snapshot['device']['id']]})],
accept=ANY) accept=ANY)
@ -50,7 +57,7 @@ def test_erasure_certificate_private_query(user: UserClient):
assert '<html' in doc assert '<html' in doc
assert '2018' in doc assert '2018' in doc
doc, response = user.get(res=docs.DocumentDef.t, doc, response = user.get(res=documents.DocumentDef.t,
item='erasures/', item='erasures/',
query=[ query=[
('filter', {'id': [snapshot['device']['id']]}), ('filter', {'id': [snapshot['device']['id']]}),
@ -60,6 +67,159 @@ def test_erasure_certificate_private_query(user: UserClient):
assert 'application/pdf' == response.content_type assert 'application/pdf' == response.content_type
@pytest.mark.mvp
def test_erasure_certificate_wrong_id(client: Client): def test_erasure_certificate_wrong_id(client: Client):
client.get(res=docs.DocumentDef.t, item='erasures/this-is-not-an-id', client.get(res=documents.DocumentDef.t, item='erasures/this-is-not-an-id',
status=teal.marshmallow.ValidationError) status=teal.marshmallow.ValidationError)
@pytest.mark.mvp
def test_export_basic_snapshot(user: UserClient):
"""Test export device information in a csv file."""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('basic.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert isinstance(datetime.strptime(export_csv[1][8], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8] + fixture_csv[1][9:]
export_csv[1] = export_csv[1][:8] + export_csv[1][9:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
@pytest.mark.mvp
def test_export_full_snapshot(user: UserClient):
"""Test a export device with all information and a lot of components."""
snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('real-eee-1001pxd.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert isinstance(datetime.strptime(export_csv[1][8], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8] + fixture_csv[1][9:]
export_csv[1] = export_csv[1][:8] + export_csv[1][9:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
@pytest.mark.mvp
def test_export_empty(user: UserClient):
"""Test to check works correctly exporting csv without any information,
export a placeholder device.
"""
csv_str, _ = user.get(res=documents.DocumentDef.t,
accept='text/csv',
item='devices/')
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
assert len(export_csv) == 0, 'Csv is not empty'
@pytest.mark.mvp
def test_export_computer_monitor(user: UserClient):
"""Test a export device type computer monitor."""
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['ComputerMonitor']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('computer-monitor.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8]
export_csv[1] = export_csv[1][:8]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
def test_export_keyboard(user: UserClient):
"""Test a export device type keyboard."""
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Keyboard']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('keyboard.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8]
export_csv[1] = export_csv[1][:8]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
@pytest.mark.mvp
def test_export_multiple_different_devices(user: UserClient):
"""Test function 'Export' of multiple different device types (like
computers, keyboards, monitors, etc..)
"""
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('multiples_devices.csv').open() \
as csv_file:
fixture_csv = list(csv.reader(csv_file))
for row in fixture_csv:
del row[8] # We remove the 'Registered in' column
# Post all devices snapshots
snapshot_pc, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
snapshot_empty, _ = user.post(file('basic.snapshot'), res=Snapshot)
snapshot_keyboard, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
snapshot_monitor, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
query=[('filter', {'type': ['Computer', 'Keyboard', 'Monitor']})],
accept='text/csv')
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
for row in export_csv:
del row[8]
assert fixture_csv == export_csv

View File

@ -1,9 +0,0 @@
from ereuse_devicehub.devicehub import Devicehub
def test_dummy(_app: Devicehub):
"""Tests the dummy cli command."""
runner = _app.test_cli_runner()
runner.invoke('dummy', '--yes')
with _app.app_context():
_app.db.drop_all()

View File

@ -61,6 +61,7 @@ def tdb2(config):
return Devicehub(inventory='tdb2', config=config, db=db) return Devicehub(inventory='tdb2', config=config, db=db)
@pytest.mark.mvp
def test_inventory_create_delete_user(cli, tdb1, tdb2): def test_inventory_create_delete_user(cli, tdb1, tdb2):
"""Tests creating two inventories with users, one user has """Tests creating two inventories with users, one user has
access to the first inventory and the other to both. Finally, deletes access to the first inventory and the other to both. Finally, deletes
@ -136,6 +137,7 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2):
assert db.has_schema('tdb2') assert db.has_schema('tdb2')
@pytest.mark.mvp
def test_create_existing_inventory(cli, tdb1): def test_create_existing_inventory(cli, tdb1):
"""Tries to create twice the same inventory.""" """Tries to create twice the same inventory."""
cli.inv('tdb1') cli.inv('tdb1')

View File

@ -22,6 +22,7 @@ from tests import conftest
""" """
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_lot_model_children(): def test_lot_model_children():
"""Tests the property Lot.children """Tests the property Lot.children
@ -65,6 +66,7 @@ def test_lot_model_children():
assert not l3.parents assert not l3.parents
@pytest.mark.mvp
def test_lot_modify_patch_endpoint_and_delete(user: UserClient): def test_lot_modify_patch_endpoint_and_delete(user: UserClient):
"""Creates and modifies lot properties through the endpoint.""" """Creates and modifies lot properties through the endpoint."""
l, _ = user.post({'name': 'foo', 'description': 'baz'}, res=Lot) l, _ = user.post({'name': 'foo', 'description': 'baz'}, res=Lot)
@ -79,6 +81,7 @@ def test_lot_modify_patch_endpoint_and_delete(user: UserClient):
user.get(res=Lot, item=l['id'], status=404) user.get(res=Lot, item=l['id'], status=404)
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_lot_device_relationship(): def test_lot_device_relationship():
device = Desktop(serial_number='foo', device = Desktop(serial_number='foo',
@ -285,6 +288,7 @@ def test_lot_unite_graphs_and_find():
assert l4 not in l3 and l5 not in l3 and l6 not in l3 and l7 not in l3 and l8 not in l3 assert l4 not in l3 and l5 not in l3 and l6 not in l3 and l7 not in l3 and l8 not in l3
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_lot_roots(): def test_lot_roots():
"""Tests getting the method Lot.roots.""" """Tests getting the method Lot.roots."""
@ -298,6 +302,7 @@ def test_lot_roots():
assert set(Lot.roots()) == {l1, l3} assert set(Lot.roots()) == {l1, l3}
@pytest.mark.mvp
def test_post_get_lot(user: UserClient): def test_post_get_lot(user: UserClient):
"""Tests submitting and retreiving a basic lot.""" """Tests submitting and retreiving a basic lot."""
l, _ = user.post({'name': 'Foo'}, res=Lot) l, _ = user.post({'name': 'Foo'}, res=Lot)
@ -345,6 +350,8 @@ def test_lot_post_add_children_view_ui_tree_normal(user: UserClient):
assert lots[0]['name'] == 'Parent' assert lots[0]['name'] == 'Parent'
@pytest.mark.mvp
@pytest.mark.xfail(reason='It needs to be fixed.')
def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient): def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
"""Tests adding a device to a lot using POST and """Tests adding a device to a lot using POST and
removing it with DELETE. removing it with DELETE.

View File

View File

@ -15,6 +15,7 @@ from tests import conftest
from tests.conftest import file from tests.conftest import file
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_workbench_rate_db(): def test_workbench_rate_db():
rate = RateComputer(processor=0.1, rate = RateComputer(processor=0.1,
@ -82,18 +83,19 @@ def test_price_from_rate():
device=pc) device=pc)
_, price = RateComputer.compute(pc) _, price = RateComputer.compute(pc)
assert price.price == Decimal('92.2001') assert price.price == Decimal('78.2001')
assert price.retailer.standard.amount == Decimal('40.9714') assert price.retailer.standard.amount == Decimal('34.7502')
assert price.platform.standard.amount == Decimal('18.8434') assert price.platform.standard.amount == Decimal('15.9821')
assert price.refurbisher.standard.amount == Decimal('32.3853') assert price.refurbisher.standard.amount == Decimal('27.4678')
assert price.price >= price.retailer.standard.amount + price.platform.standard.amount \ assert price.price >= price.retailer.standard.amount + price.platform.standard.amount \
+ price.refurbisher.standard.amount + price.refurbisher.standard.amount
assert price.retailer.warranty2.amount == Decimal('55.3085') assert price.retailer.warranty2.amount == Decimal('46.9103')
assert price.platform.warranty2.amount == Decimal('25.4357') assert price.platform.warranty2.amount == Decimal('21.5735')
assert price.refurbisher.warranty2.amount == Decimal('43.7259') assert price.refurbisher.warranty2.amount == Decimal('37.0864')
assert price.warranty2 == Decimal('124.47') assert price.warranty2 == Decimal('105.57')
@pytest.mark.mvp
def test_when_rate_must_not_compute(user: UserClient): def test_when_rate_must_not_compute(user: UserClient):
"""Test to check if rate is computed in case of should not be calculated: """Test to check if rate is computed in case of should not be calculated:
1. Snapshot haven't visual test 1. Snapshot haven't visual test
@ -130,6 +132,7 @@ def test_when_rate_must_not_compute(user: UserClient):
assert 'rate' not in snapshot['device'] assert 'rate' not in snapshot['device']
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_multiple_rates(user: UserClient): def test_multiple_rates(user: UserClient):
"""Tests submitting two rates from Workbench, """Tests submitting two rates from Workbench,
@ -164,12 +167,12 @@ def test_multiple_rates(user: UserClient):
assert rate1.processor == 3.95 assert rate1.processor == 3.95
assert rate1.ram == 3.8 assert rate1.ram == 3.8
assert rate1.appearance == 0.3 assert rate1.appearance is None
assert rate1.functionality == 0.4 assert rate1.functionality is None
assert rate1.rating == 4.62 assert rate1.rating == 3.92
assert price1.price == Decimal('92.4001') assert price1.price == Decimal('78.4001')
cpu.actions_one.add(BenchmarkProcessor(rate=16069.44)) cpu.actions_one.add(BenchmarkProcessor(rate=16069.44))
ssd = SolidStateDrive(size=476940) ssd = SolidStateDrive(size=476940)
@ -191,9 +194,9 @@ def test_multiple_rates(user: UserClient):
assert rate2.processor == 3.78 assert rate2.processor == 3.78
assert rate2.ram == 3.95 assert rate2.ram == 3.95
assert rate2.appearance == 0 assert rate2.appearance is None
assert rate2.functionality == -0.5 assert rate2.functionality is None
assert rate2.rating == 3.43 assert rate2.rating == 3.93
assert price2.price == Decimal('68.6001') assert price2.price == Decimal('78.6001')

View File

@ -28,6 +28,7 @@ from ereuse_devicehub.resources.enums import AppearanceRange, ComputerChassis, F
from tests import conftest from tests import conftest
@pytest.mark.mvp
def test_rate_data_storage_rate(): def test_rate_data_storage_rate():
"""Test to check if compute data storage rate have same value than """Test to check if compute data storage rate have same value than
previous score version. previous score version.
@ -63,6 +64,7 @@ def test_rate_data_storage_rate():
assert math.isclose(data_storage_rate, 3.70, rel_tol=0.001) assert math.isclose(data_storage_rate, 3.70, rel_tol=0.001)
@pytest.mark.mvp
def test_rate_data_storage_size_is_null(): def test_rate_data_storage_size_is_null():
"""Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0, """Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected; BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected;
@ -75,6 +77,7 @@ def test_rate_data_storage_size_is_null():
assert data_storage_rate is None assert data_storage_rate is None
@pytest.mark.mvp
def test_rate_no_data_storage(): def test_rate_no_data_storage():
"""Test without data storage devices.""" """Test without data storage devices."""
@ -84,6 +87,7 @@ def test_rate_no_data_storage():
assert data_storage_rate is None assert data_storage_rate is None
@pytest.mark.mvp
def test_rate_ram_rate(): def test_rate_ram_rate():
"""Test to check if compute ram rate have same value than previous """Test to check if compute ram rate have same value than previous
score version only with 1 RamModule. score version only with 1 RamModule.
@ -96,6 +100,7 @@ def test_rate_ram_rate():
assert math.isclose(ram_rate, 2.02, rel_tol=0.002), 'RamRate returns incorrect value(rate)' assert math.isclose(ram_rate, 2.02, rel_tol=0.002), 'RamRate returns incorrect value(rate)'
@pytest.mark.mvp
def test_rate_ram_rate_2modules(): def test_rate_ram_rate_2modules():
"""Test to check if compute ram rate have same value than previous """Test to check if compute ram rate have same value than previous
score version with 2 RamModule. score version with 2 RamModule.
@ -109,6 +114,7 @@ def test_rate_ram_rate_2modules():
assert math.isclose(ram_rate, 3.79, rel_tol=0.001), 'RamRate returns incorrect value(rate)' assert math.isclose(ram_rate, 3.79, rel_tol=0.001), 'RamRate returns incorrect value(rate)'
@pytest.mark.mvp
def test_rate_ram_rate_4modules(): def test_rate_ram_rate_4modules():
"""Test to check if compute ram rate have same value than previous """Test to check if compute ram rate have same value than previous
score version with 2 RamModule. score version with 2 RamModule.
@ -124,6 +130,7 @@ def test_rate_ram_rate_4modules():
assert math.isclose(ram_rate, 1.993, rel_tol=0.001), 'RamRate returns incorrect value(rate)' assert math.isclose(ram_rate, 1.993, rel_tol=0.001), 'RamRate returns incorrect value(rate)'
@pytest.mark.mvp
def test_rate_ram_module_size_is_0(): def test_rate_ram_module_size_is_0():
"""Test where input data RamModule.size = 0; is like no RamModule """Test where input data RamModule.size = 0; is like no RamModule
has been detected. has been detected.
@ -135,6 +142,7 @@ def test_rate_ram_module_size_is_0():
assert ram_rate is None assert ram_rate is None
@pytest.mark.mvp
def test_rate_ram_speed_is_null(): def test_rate_ram_speed_is_null():
"""Test where RamModule.speed is NULL (not detected) but has size.""" """Test where RamModule.speed is NULL (not detected) but has size."""
@ -151,6 +159,7 @@ def test_rate_ram_speed_is_null():
assert math.isclose(ram_rate, 1.25, rel_tol=0.004), 'RamRate returns incorrect value(rate)' assert math.isclose(ram_rate, 1.25, rel_tol=0.004), 'RamRate returns incorrect value(rate)'
@pytest.mark.mvp
def test_rate_no_ram_module(): def test_rate_no_ram_module():
"""Test without RamModule.""" """Test without RamModule."""
ram0 = RamModule() ram0 = RamModule()
@ -159,6 +168,7 @@ def test_rate_no_ram_module():
assert ram_rate is None assert ram_rate is None
@pytest.mark.mvp
def test_rate_processor_rate(): def test_rate_processor_rate():
"""Test to check if compute processor rate have same value than previous """Test to check if compute processor rate have same value than previous
score version only with 1 core. score version only with 1 core.
@ -173,6 +183,7 @@ def test_rate_processor_rate():
assert math.isclose(processor_rate, 1, rel_tol=0.001) assert math.isclose(processor_rate, 1, rel_tol=0.001)
@pytest.mark.mvp
def test_rate_processor_rate_2cores(): def test_rate_processor_rate_2cores():
"""Test to check if compute processor rate have same value than previous """Test to check if compute processor rate have same value than previous
score version with 2 cores. score version with 2 cores.
@ -194,6 +205,7 @@ def test_rate_processor_rate_2cores():
assert math.isclose(processor_rate, 3.93, rel_tol=0.002) assert math.isclose(processor_rate, 3.93, rel_tol=0.002)
@pytest.mark.mvp
def test_rate_processor_with_null_cores(): def test_rate_processor_with_null_cores():
"""Test with processor device have null number of cores.""" """Test with processor device have null number of cores."""
cpu = Processor(cores=None, speed=3.3) cpu = Processor(cores=None, speed=3.3)
@ -204,6 +216,7 @@ def test_rate_processor_with_null_cores():
assert math.isclose(processor_rate, 1.38, rel_tol=0.003) assert math.isclose(processor_rate, 1.38, rel_tol=0.003)
@pytest.mark.mvp
def test_rate_processor_with_null_speed(): def test_rate_processor_with_null_speed():
"""Test with processor device have null speed value.""" """Test with processor device have null speed value."""
cpu = Processor(cores=1, speed=None) cpu = Processor(cores=1, speed=None)
@ -214,6 +227,7 @@ def test_rate_processor_with_null_speed():
assert math.isclose(processor_rate, 1.06, rel_tol=0.001) assert math.isclose(processor_rate, 1.06, rel_tol=0.001)
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_1193(): def test_rate_computer_1193():
"""Test rate computer characteristics: """Test rate computer characteristics:
@ -264,9 +278,10 @@ def test_rate_computer_1193():
assert math.isclose(rate_pc.processor, 3.95, rel_tol=0.001) assert math.isclose(rate_pc.processor, 3.95, rel_tol=0.001)
assert math.isclose(rate_pc.rating, 4.61, rel_tol=0.001) assert math.isclose(rate_pc.rating, 3.91, rel_tol=0.001)
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_1201(): def test_rate_computer_1201():
"""Test rate computer characteristics: """Test rate computer characteristics:
@ -315,9 +330,10 @@ def test_rate_computer_1201():
assert math.isclose(rate_pc.processor, 3.93, rel_tol=0.001) assert math.isclose(rate_pc.processor, 3.93, rel_tol=0.001)
assert math.isclose(rate_pc.rating, 3.48, rel_tol=0.001) assert math.isclose(rate_pc.rating, 3.08, rel_tol=0.001)
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_multiple_ram_module(): def test_rate_computer_multiple_ram_module():
"""Test rate computer characteristics: """Test rate computer characteristics:
@ -373,9 +389,10 @@ def test_rate_computer_multiple_ram_module():
assert math.isclose(rate_pc.processor, 1, rel_tol=0.001) assert math.isclose(rate_pc.processor, 1, rel_tol=0.001)
assert rate_pc.rating == 1.57 assert rate_pc.rating == 1.37
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_one_ram_module(): def test_rate_computer_one_ram_module():
"""Test rate computer characteristics: """Test rate computer characteristics:
@ -426,7 +443,7 @@ def test_rate_computer_one_ram_module():
assert math.isclose(rate_pc.processor, 4.09, rel_tol=0.001) assert math.isclose(rate_pc.processor, 4.09, rel_tol=0.001)
assert math.isclose(rate_pc.rating, 2.5, rel_tol=0.001) assert math.isclose(rate_pc.rating, 2.1, rel_tol=0.001)
@pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark') @pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark')

View File

@ -1,156 +0,0 @@
import csv
from datetime import datetime
from io import StringIO
from pathlib import Path
from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.action.models import Snapshot
from ereuse_devicehub.resources.documents import documents
from tests.conftest import file
def test_export_basic_snapshot(user: UserClient):
"""Test export device information in a csv file."""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('basic.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert isinstance(datetime.strptime(export_csv[1][8], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8] + fixture_csv[1][9:]
export_csv[1] = export_csv[1][:8] + export_csv[1][9:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
def test_export_full_snapshot(user: UserClient):
"""Test a export device with all information and a lot of components."""
snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('real-eee-1001pxd.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert isinstance(datetime.strptime(export_csv[1][8], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8] + fixture_csv[1][9:]
export_csv[1] = export_csv[1][:8] + export_csv[1][9:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
def test_export_empty(user: UserClient):
"""Test to check works correctly exporting csv without any information,
export a placeholder device.
"""
csv_str, _ = user.get(res=documents.DocumentDef.t,
accept='text/csv',
item='devices/')
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
assert len(export_csv) == 0, 'Csv is not empty'
def test_export_computer_monitor(user: UserClient):
"""Test a export device type computer monitor."""
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['ComputerMonitor']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('computer-monitor.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8]
export_csv[1] = export_csv[1][:8]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
def test_export_keyboard(user: UserClient):
"""Test a export device type keyboard."""
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Keyboard']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('keyboard.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8]
export_csv[1] = export_csv[1][:8]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
def test_export_multiple_different_devices(user: UserClient):
"""Test function 'Export' of multiple different device types (like
computers, keyboards, monitors, etc..)
"""
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('multiples_devices.csv').open() \
as csv_file:
fixture_csv = list(csv.reader(csv_file))
for row in fixture_csv:
del row[8] # We remove the 'Registered in' column
# Post all devices snapshots
snapshot_pc, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
snapshot_empty, _ = user.post(file('basic.snapshot'), res=Snapshot)
snapshot_keyboard, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
snapshot_monitor, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
query=[('filter', {'type': ['Computer', 'Keyboard', 'Monitor']})],
accept='text/csv')
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
for row in export_csv:
del row[8]
assert fixture_csv == export_csv

View File

@ -12,8 +12,7 @@ from ereuse_devicehub.client import UserClient
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.devicehub import Devicehub from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.resources.action.models import Action, BenchmarkDataStorage, \ from ereuse_devicehub.resources.action.models import Action, BenchmarkDataStorage, \
BenchmarkProcessor, EraseSectors, RateComputer, Snapshot, SnapshotRequest, VisualTest, \ BenchmarkProcessor, EraseSectors, RateComputer, Snapshot, SnapshotRequest, VisualTest
MeasureBattery, BenchmarkRamSysbench, StressTest
from ereuse_devicehub.resources.device import models as m from ereuse_devicehub.resources.device import models as m
from ereuse_devicehub.resources.device.exceptions import NeedsId from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.models import SolidStateDrive from ereuse_devicehub.resources.device.models import SolidStateDrive
@ -25,6 +24,7 @@ from ereuse_devicehub.resources.user.models import User
from tests.conftest import file from tests.conftest import file
@pytest.mark.mvp
@pytest.mark.usefixtures('auth_app_context') @pytest.mark.usefixtures('auth_app_context')
def test_snapshot_model(): def test_snapshot_model():
"""Tests creating a Snapshot with its relationships ensuring correct """Tests creating a Snapshot with its relationships ensuring correct
@ -55,12 +55,14 @@ def test_snapshot_model():
assert device.url == urlutils.URL('http://localhost/devices/1') assert device.url == urlutils.URL('http://localhost/devices/1')
@pytest.mark.mvp
def test_snapshot_schema(app: Devicehub): def test_snapshot_schema(app: Devicehub):
with app.app_context(): with app.app_context():
s = file('basic.snapshot') s = file('basic.snapshot')
app.resources['Snapshot'].schema.load(s) app.resources['Snapshot'].schema.load(s)
@pytest.mark.mvp
def test_snapshot_post(user: UserClient): def test_snapshot_post(user: UserClient):
"""Tests the post snapshot endpoint (validation, etc), data correctness, """Tests the post snapshot endpoint (validation, etc), data correctness,
and relationship correctness. and relationship correctness.
@ -96,6 +98,8 @@ def test_snapshot_post(user: UserClient):
assert rate['snapshot']['id'] == snapshot['id'] assert rate['snapshot']['id'] == snapshot['id']
@pytest.mark.mvp
@pytest.mark.xfail(reason='Needs to fix it')
def test_snapshot_component_add_remove(user: UserClient): def test_snapshot_component_add_remove(user: UserClient):
"""Tests adding and removing components and some don't generate HID. """Tests adding and removing components and some don't generate HID.
All computers generate HID. All computers generate HID.
@ -229,6 +233,8 @@ def _test_snapshot_computer_no_hid(user: UserClient):
user.post(s, res=Snapshot) user.post(s, res=Snapshot)
@pytest.mark.mvp
@pytest.mark.xfail(reason='Needs to fix it')
def test_snapshot_post_without_hid(user: UserClient): def test_snapshot_post_without_hid(user: UserClient):
"""Tests the post snapshot endpoint (validation, etc), data correctness, """Tests the post snapshot endpoint (validation, etc), data correctness,
and relationship correctness with HID field generated with type - model - manufacturer - S/N. and relationship correctness with HID field generated with type - model - manufacturer - S/N.
@ -247,10 +253,12 @@ def test_snapshot_post_without_hid(user: UserClient):
assert snapshot['author']['id'] == user.user['id'] assert snapshot['author']['id'] == user.user['id']
assert 'actions' not in snapshot['device'] assert 'actions' not in snapshot['device']
assert 'author' not in snapshot['device'] assert 'author' not in snapshot['device']
assert snapshot['severity'] == 'Warning'
response = user.post(snapshot, res=Snapshot) response = user.post(snapshot, res=Snapshot)
assert response.status == 201 assert response.status == 201
@pytest.mark.mvp
def test_snapshot_mismatch_id(): def test_snapshot_mismatch_id():
"""Tests uploading a device with an ID from another device.""" """Tests uploading a device with an ID from another device."""
# Note that this won't happen as in this new version # Note that this won't happen as in this new version
@ -258,6 +266,7 @@ def test_snapshot_mismatch_id():
pass pass
@pytest.mark.mvp
def test_snapshot_tag_inner_tag(tag_id: str, user: UserClient, app: Devicehub): def test_snapshot_tag_inner_tag(tag_id: str, user: UserClient, app: Devicehub):
"""Tests a posting Snapshot with a local tag.""" """Tests a posting Snapshot with a local tag."""
b = file('basic.snapshot') b = file('basic.snapshot')
@ -270,6 +279,7 @@ def test_snapshot_tag_inner_tag(tag_id: str, user: UserClient, app: Devicehub):
assert tag.device_id == 1, 'Tag should be linked to the first device' assert tag.device_id == 1, 'Tag should be linked to the first device'
@pytest.mark.mvp
def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid(user: UserClient, tag_id: str): def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid(user: UserClient, tag_id: str):
"""Ensures one device cannot 'steal' the tag from another one.""" """Ensures one device cannot 'steal' the tag from another one."""
pc1 = file('basic.snapshot') pc1 = file('basic.snapshot')
@ -281,6 +291,7 @@ def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid(user: UserClient,
user.post(pc2, res=Snapshot, status=MismatchBetweenTagsAndHid) user.post(pc2, res=Snapshot, status=MismatchBetweenTagsAndHid)
@pytest.mark.mvp
def test_snapshot_different_properties_same_tags(user: UserClient, tag_id: str): def test_snapshot_different_properties_same_tags(user: UserClient, tag_id: str):
"""Tests a snapshot performed to device 1 with tag A and then to """Tests a snapshot performed to device 1 with tag A and then to
device 2 with tag B. Both don't have HID but are different type. device 2 with tag B. Both don't have HID but are different type.
@ -300,12 +311,14 @@ def test_snapshot_different_properties_same_tags(user: UserClient, tag_id: str):
user.post(pc2, res=Snapshot, status=MismatchBetweenProperties) user.post(pc2, res=Snapshot, status=MismatchBetweenProperties)
@pytest.mark.mvp
def test_snapshot_upload_twice_uuid_error(user: UserClient): def test_snapshot_upload_twice_uuid_error(user: UserClient):
pc1 = file('basic.snapshot') pc1 = file('basic.snapshot')
user.post(pc1, res=Snapshot) user.post(pc1, res=Snapshot)
user.post(pc1, res=Snapshot, status=UniqueViolation) user.post(pc1, res=Snapshot, status=UniqueViolation)
@pytest.mark.mvp
def test_snapshot_component_containing_components(user: UserClient): def test_snapshot_component_containing_components(user: UserClient):
"""There is no reason for components to have components and when """There is no reason for components to have components and when
this happens it is always an error. this happens it is always an error.
@ -322,6 +335,8 @@ def test_snapshot_component_containing_components(user: UserClient):
user.post(s, res=Snapshot, status=ValidationError) user.post(s, res=Snapshot, status=ValidationError)
@pytest.mark.mvp
@pytest.mark.xfail(reason='It needs to be fixed.')
def test_erase_privacy_standards_endtime_sort(user: UserClient): def test_erase_privacy_standards_endtime_sort(user: UserClient):
"""Tests a Snapshot with EraseSectors and the resulting privacy """Tests a Snapshot with EraseSectors and the resulting privacy
properties. properties.
@ -401,37 +416,6 @@ def test_test_data_storage(user: UserClient):
assert incidence_test['severity'] == 'Error' assert incidence_test['severity'] == 'Error'
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_computer_monitor(user: UserClient):
"""Tests that a snapshot of computer monitor device create correctly."""
s = file('computer-monitor.snapshot')
snapshot_and_check(user, s, action_types=('RateMonitor',))
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient):
"""Tests that a snapshot of smartphone device is creat correctly."""
s = file('smartphone.snapshot')
snapshot = snapshot_and_check(user, s, action_types=('VisualTest',))
mobile, _ = user.get(res=m.Device, item=snapshot['device']['id'])
assert mobile['imei'] == 3568680000414120
@pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_none():
"""Tests that a snapshot without components does not remove them
from the computer.
"""
# TODO JN is really necessary in which cases??
@pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_empty():
"""Tests that a snapshot whose components are an empty list remove
all its components.
"""
def assert_similar_device(device1: dict, device2: dict): def assert_similar_device(device1: dict, device2: dict):
"""Like :class:`ereuse_devicehub.resources.device.models.Device. """Like :class:`ereuse_devicehub.resources.device.models.Device.
is_similar()` but adapted for testing. is_similar()` but adapted for testing.
@ -497,14 +481,7 @@ def snapshot_and_check(user: UserClient,
return snapshot return snapshot
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it') @pytest.mark.mvp
def test_snapshot_keyboard(user: UserClient):
s = file('keyboard.snapshot')
snapshot = snapshot_and_check(user, s, action_types=('VisualTest',))
keyboard = snapshot['device']
assert keyboard['layout'] == 'ES'
@pytest.mark.xfail(reason='Debug and rewrite it') @pytest.mark.xfail(reason='Debug and rewrite it')
def test_pc_rating_rate_none(user: UserClient): def test_pc_rating_rate_none(user: UserClient):
"""Tests a Snapshot with EraseSectors.""" """Tests a Snapshot with EraseSectors."""
@ -513,14 +490,7 @@ def test_pc_rating_rate_none(user: UserClient):
snapshot, _ = user.post(res=Snapshot, data=s) snapshot, _ = user.post(res=Snapshot, data=s)
@pytest.mark.mvp
def test_pc_2(user: UserClient): def test_pc_2(user: UserClient):
s = file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot') s = file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot')
snapshot, _ = user.post(res=Snapshot, data=s) snapshot, _ = user.post(res=Snapshot, data=s)
@pytest.mark.xfail(reason='Add battery component assets')
def test_snapshot_pc_with_battery_component(user: UserClient):
pc1 = file('acer.happy.battery.snapshot')
snapshot = snapshot_and_check(user, pc1,
action_types=(StressTest.t, BenchmarkRamSysbench.t),
perform_second_snapshot=False)

View File

@ -22,6 +22,7 @@ from tests import conftest
from tests.conftest import file from tests.conftest import file
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_create_tag(): def test_create_tag():
"""Creates a tag specifying a custom organization.""" """Creates a tag specifying a custom organization."""
@ -34,6 +35,7 @@ def test_create_tag():
assert tag.provider == URL('http://foo.bar') assert tag.provider == URL('http://foo.bar')
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_create_tag_default_org(): def test_create_tag_default_org():
"""Creates a tag using the default organization.""" """Creates a tag using the default organization."""
@ -47,6 +49,7 @@ def test_create_tag_default_org():
assert tag.org.name == 'FooOrg' # as defined in the settings assert tag.org.name == 'FooOrg' # as defined in the settings
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_create_tag_no_slash(): def test_create_tag_no_slash():
"""Checks that no tags can be created that contain a slash.""" """Checks that no tags can be created that contain a slash."""
@ -57,6 +60,7 @@ def test_create_tag_no_slash():
Tag('bar', secondary='/') Tag('bar', secondary='/')
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_create_two_same_tags(): def test_create_two_same_tags():
"""Ensures there cannot be two tags with the same ID and organization.""" """Ensures there cannot be two tags with the same ID and organization."""
@ -72,6 +76,7 @@ def test_create_two_same_tags():
db.session.commit() db.session.commit()
@pytest.mark.mvp
def test_tag_post(app: Devicehub, user: UserClient): def test_tag_post(app: Devicehub, user: UserClient):
"""Checks the POST method of creating a tag.""" """Checks the POST method of creating a tag."""
user.post({'id': 'foo'}, res=Tag) user.post({'id': 'foo'}, res=Tag)
@ -79,6 +84,7 @@ def test_tag_post(app: Devicehub, user: UserClient):
assert Tag.query.filter_by(id='foo').one() assert Tag.query.filter_by(id='foo').one()
@pytest.mark.mvp
def test_tag_post_etag(user: UserClient): def test_tag_post_etag(user: UserClient):
"""Ensures users cannot create eReuse.org tags through POST; """Ensures users cannot create eReuse.org tags through POST;
only terminal. only terminal.
@ -93,12 +99,13 @@ def test_tag_post_etag(user: UserClient):
user.post({'id': 'FOO-123456'}, res=Tag) user.post({'id': 'FOO-123456'}, res=Tag)
@pytest.mark.mvp
def test_tag_get_device_from_tag_endpoint(app: Devicehub, user: UserClient): def test_tag_get_device_from_tag_endpoint(app: Devicehub, user: UserClient):
"""Checks getting a linked device from a tag endpoint""" """Checks getting a linked device from a tag endpoint"""
with app.app_context(): with app.app_context():
# Create a pc with a tag # Create a pc with a tag
tag = Tag(id='foo-bar') tag = Tag(id='foo-bar')
pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower) pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id'])
pc.tags.add(tag) pc.tags.add(tag)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -106,6 +113,7 @@ def test_tag_get_device_from_tag_endpoint(app: Devicehub, user: UserClient):
assert computer['serialNumber'] == 'sn1' assert computer['serialNumber'] == 'sn1'
@pytest.mark.mvp
def test_tag_get_device_from_tag_endpoint_no_linked(app: Devicehub, user: UserClient): def test_tag_get_device_from_tag_endpoint_no_linked(app: Devicehub, user: UserClient):
"""As above, but when the tag is not linked.""" """As above, but when the tag is not linked."""
with app.app_context(): with app.app_context():
@ -114,11 +122,13 @@ def test_tag_get_device_from_tag_endpoint_no_linked(app: Devicehub, user: UserCl
user.get(res=Tag, item='foo-bar/device', status=TagNotLinked) user.get(res=Tag, item='foo-bar/device', status=TagNotLinked)
@pytest.mark.mvp
def test_tag_get_device_from_tag_endpoint_no_tag(user: UserClient): def test_tag_get_device_from_tag_endpoint_no_tag(user: UserClient):
"""As above, but when there is no tag with such ID.""" """As above, but when there is no tag with such ID."""
user.get(res=Tag, item='foo-bar/device', status=ResourceNotFound) user.get(res=Tag, item='foo-bar/device', status=ResourceNotFound)
@pytest.mark.mvp
def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: UserClient): def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: UserClient):
"""As above, but when there are two tags with the same ID, the """As above, but when there are two tags with the same ID, the
system should not return any of both (to be deterministic) so system should not return any of both (to be deterministic) so
@ -132,6 +142,7 @@ def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: Us
user.get(res=Tag, item='foo-bar/device', status=MultipleResourcesFound) user.get(res=Tag, item='foo-bar/device', status=MultipleResourcesFound)
@pytest.mark.mvp
def test_tag_create_tags_cli(app: Devicehub, user: UserClient): def test_tag_create_tags_cli(app: Devicehub, user: UserClient):
"""Checks creating tags with the CLI endpoint.""" """Checks creating tags with the CLI endpoint."""
runner = app.test_cli_runner() runner = app.test_cli_runner()
@ -142,6 +153,7 @@ def test_tag_create_tags_cli(app: Devicehub, user: UserClient):
assert tag.org.id == Organization.get_default_org_id() assert tag.org.id == Organization.get_default_org_id()
@pytest.mark.mvp
def test_tag_create_etags_cli(app: Devicehub, user: UserClient): def test_tag_create_etags_cli(app: Devicehub, user: UserClient):
"""Creates an eTag through the CLI.""" """Creates an eTag through the CLI."""
# todo what happens to organization? # todo what happens to organization?
@ -154,6 +166,7 @@ def test_tag_create_etags_cli(app: Devicehub, user: UserClient):
assert tag.provider == URL('https://t.ereuse.org') assert tag.provider == URL('https://t.ereuse.org')
@pytest.mark.mvp
def test_tag_manual_link_search(app: Devicehub, user: UserClient): def test_tag_manual_link_search(app: Devicehub, user: UserClient):
"""Tests linking manually a tag through PUT /tags/<id>/device/<id> """Tests linking manually a tag through PUT /tags/<id>/device/<id>
@ -161,7 +174,7 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient):
""" """
with app.app_context(): with app.app_context():
db.session.add(Tag('foo-bar', secondary='foo-sec')) db.session.add(Tag('foo-bar', secondary='foo-sec'))
desktop = Desktop(serial_number='foo', chassis=ComputerChassis.AllInOne) desktop = Desktop(serial_number='foo', chassis=ComputerChassis.AllInOne, owner_id=user.user['id'])
db.session.add(desktop) db.session.add(desktop)
db.session.commit() db.session.commit()
desktop_id = desktop.id desktop_id = desktop.id
@ -189,6 +202,7 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient):
assert i['items'] assert i['items']
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_tag_secondary_workbench_link_find(user: UserClient): def test_tag_secondary_workbench_link_find(user: UserClient):
"""Creates and consumes tags with a secondary id, linking them """Creates and consumes tags with a secondary id, linking them
@ -215,6 +229,7 @@ def test_tag_secondary_workbench_link_find(user: UserClient):
assert len(r['items']) == 1 assert len(r['items']) == 1
@pytest.mark.mvp
def test_tag_create_tags_cli_csv(app: Devicehub, user: UserClient): def test_tag_create_tags_cli_csv(app: Devicehub, user: UserClient):
"""Checks creating tags with the CLI endpoint using a CSV.""" """Checks creating tags with the CLI endpoint using a CSV."""
csv = pathlib.Path(__file__).parent / 'files' / 'tags-cli.csv' csv = pathlib.Path(__file__).parent / 'files' / 'tags-cli.csv'
@ -232,7 +247,8 @@ def test_tag_multiple_secondary_org(user: UserClient):
user.post({'id': 'foo1', 'secondary': 'bar'}, res=Tag, status=UniqueViolation) user.post({'id': 'foo1', 'secondary': 'bar'}, res=Tag, status=UniqueViolation)
def test_crate_num_regular_tags(user: UserClient, requests_mock: requests_mock.mocker.Mocker): @pytest.mark.mvp
def test_create_num_regular_tags(user: UserClient, requests_mock: requests_mock.mocker.Mocker):
"""Create regular tags. This is done using a tag provider that """Create regular tags. This is done using a tag provider that
returns IDs. These tags are printable. returns IDs. These tags are printable.
""" """
@ -252,6 +268,7 @@ def test_crate_num_regular_tags(user: UserClient, requests_mock: requests_mock.m
assert data['items'][1]['printable'] assert data['items'][1]['printable']
@pytest.mark.mvp
def test_get_tags_endpoint(user: UserClient, app: Devicehub, def test_get_tags_endpoint(user: UserClient, app: Devicehub,
requests_mock: requests_mock.mocker.Mocker): requests_mock: requests_mock.mocker.Mocker):
"""Performs GET /tags after creating 3 tags, 2 printable and one """Performs GET /tags after creating 3 tags, 2 printable and one

View File

@ -16,6 +16,7 @@ from ereuse_devicehub.resources.user.models import User
from tests.conftest import app_context, create_user from tests.conftest import app_context, create_user
@pytest.mark.mvp
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)
def test_create_user_method_with_agent(app: Devicehub): def test_create_user_method_with_agent(app: Devicehub):
"""Tests creating an user through the main method. """Tests creating an user through the main method.
@ -41,6 +42,7 @@ def test_create_user_method_with_agent(app: Devicehub):
assert individual.email == user.email assert individual.email == user.email
@pytest.mark.mvp
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)
def test_create_user_email_insensitive(): def test_create_user_email_insensitive():
"""Ensures email is case insensitive.""" """Ensures email is case insensitive."""
@ -53,6 +55,7 @@ def test_create_user_email_insensitive():
assert u1.email == 'foo@foo.com' assert u1.email == 'foo@foo.com'
@pytest.mark.mvp
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)
def test_hash_password(): def test_hash_password():
"""Tests correct password hashing and equaling.""" """Tests correct password hashing and equaling."""
@ -61,6 +64,7 @@ def test_hash_password():
assert user.password == 'foo' assert user.password == 'foo'
@pytest.mark.mvp
def test_login_success(client: Client, app: Devicehub): def test_login_success(client: Client, app: Devicehub):
"""Tests successfully performing login. """Tests successfully performing login.
This checks that: This checks that:
@ -83,6 +87,7 @@ def test_login_success(client: Client, app: Devicehub):
assert user['inventories'][0]['id'] == 'test' assert user['inventories'][0]['id'] == 'test'
@pytest.mark.mvp
def test_login_failure(client: Client, app: Devicehub): def test_login_failure(client: Client, app: Devicehub):
"""Tests performing wrong login.""" """Tests performing wrong login."""
# Wrong password # Wrong password

View File

@ -7,13 +7,14 @@ import pytest
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.action import models as em from ereuse_devicehub.resources.action import models as em
from ereuse_devicehub.resources.action.models import RateComputer, VisualTest from ereuse_devicehub.resources.action.models import RateComputer, BenchmarkProcessor, BenchmarkRamSysbench
from ereuse_devicehub.resources.device.exceptions import NeedsId from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
from tests.conftest import file from tests.conftest import file
@pytest.mark.mvp
def test_workbench_server_condensed(user: UserClient): def test_workbench_server_condensed(user: UserClient):
"""As :def:`.test_workbench_server_phases` but all the actions """As :def:`.test_workbench_server_phases` but all the actions
condensed in only one big ``Snapshot`` file, as described condensed in only one big ``Snapshot`` file, as described
@ -36,6 +37,7 @@ def test_workbench_server_condensed(user: UserClient):
('BenchmarkProcessorSysbench', 5), ('BenchmarkProcessorSysbench', 5),
('StressTest', 1), ('StressTest', 1),
('EraseSectors', 6), ('EraseSectors', 6),
('EreusePrice', 1),
('BenchmarkRamSysbench', 1), ('BenchmarkRamSysbench', 1),
('BenchmarkProcessor', 5), ('BenchmarkProcessor', 5),
('Install', 6), ('Install', 6),
@ -43,7 +45,6 @@ def test_workbench_server_condensed(user: UserClient):
('BenchmarkDataStorage', 6), ('BenchmarkDataStorage', 6),
('BenchmarkDataStorage', 7), ('BenchmarkDataStorage', 7),
('TestDataStorage', 6), ('TestDataStorage', 6),
('VisualTest', 1),
('RateComputer', 1) ('RateComputer', 1)
} }
assert snapshot['closed'] assert snapshot['closed']
@ -58,15 +59,14 @@ def test_workbench_server_condensed(user: UserClient):
assert device['ramSize'] == 2048, 'There are 3 RAM: 2 x 1024 and 1 None sizes' assert device['ramSize'] == 2048, 'There are 3 RAM: 2 x 1024 and 1 None sizes'
assert device['rate']['closed'] assert device['rate']['closed']
assert device['rate']['severity'] == 'Info' assert device['rate']['severity'] == 'Info'
assert device['rate']['rating'] == 0 assert device['rate']['rating'] == 1
assert device['rate']['type'] == RateComputer.t assert device['rate']['type'] == RateComputer.t
# TODO JN why haven't same order in actions?? # TODO JN why haven't same order in actions on each execution?
assert device['actions'][2]['type'] == VisualTest.t assert device['actions'][2]['type'] == BenchmarkProcessor.t or device['actions'][2]['type'] == BenchmarkRamSysbench.t
assert device['actions'][2]['appearanceRange'] == 'A'
assert device['actions'][2]['functionalityRange'] == 'B'
assert device['tags'][0]['id'] == 'tag1' assert device['tags'][0]['id'] == 'tag1'
@pytest.mark.mvp
@pytest.mark.xfail(reason='Functionality not yet developed.') @pytest.mark.xfail(reason='Functionality not yet developed.')
def test_workbench_server_phases(user: UserClient): def test_workbench_server_phases(user: UserClient):
"""Tests the phases described in the docs section `Snapshots from """Tests the phases described in the docs section `Snapshots from
@ -134,6 +134,7 @@ def test_workbench_server_phases(user: UserClient):
assert len(pc['actions']) == 10 # todo shall I add child actions? assert len(pc['actions']) == 10 # todo shall I add child actions?
@pytest.mark.mvp
def test_real_hp_11(user: UserClient): def test_real_hp_11(user: UserClient):
s = file('real-hp.snapshot.11') s = file('real-hp.snapshot.11')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
@ -160,11 +161,13 @@ def test_real_hp_11(user: UserClient):
# todo check rating # todo check rating
@pytest.mark.mvp
def test_real_toshiba_11(user: UserClient): def test_real_toshiba_11(user: UserClient):
s = file('real-toshiba.snapshot.11') s = file('real-toshiba.snapshot.11')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
@pytest.mark.mvp
def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
"""Checks the values of the device, components, """Checks the values of the device, components,
actions and their relationships of a real pc. actions and their relationships of a real pc.
@ -186,20 +189,13 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
# assert pc['actions'][0]['functionalityRange'] == 'B' # assert pc['actions'][0]['functionalityRange'] == 'B'
# TODO add appearance and functionality Range in device[rate] # TODO add appearance and functionality Range in device[rate]
assert rate['processorRange'] == 'VERY_LOW' assert rate['processorRange'] == 'LOW'
assert rate['ramRange'] == 'VERY_LOW' assert rate['ramRange'] == 'LOW'
assert rate['ratingRange'] == 'VERY_LOW' assert rate['ratingRange'] == 'LOW'
assert rate['ram'] == 1.53 assert rate['ram'] == 1.53
# TODO add camelCase instead of snake_case # TODO add camelCase instead of snake_case
assert rate['dataStorage'] == 3.76 assert rate['dataStorage'] == 3.76
assert rate['type'] == 'RateComputer' assert rate['type'] == 'RateComputer'
# TODO change pc[actions] TestBios instead of rate[biosRange]
# assert rate['biosRange'] == 'C'
assert rate['appearance'] == 0, 'appearance B equals 0 points'
# todo fix gets correctly functionality rates values not equals to 0.
assert rate['functionality'] == 0, 'functionality A equals 0.4 points'
# why this assert?? -2 < rating < 4.7
# assert rate['rating'] > 0 and rate['rating'] != 1
components = snapshot['components'] components = snapshot['components']
wifi = components[0] wifi = components[0]
assert wifi['hid'] == 'networkadapter-qualcomm_atheros-' \ assert wifi['hid'] == 'networkadapter-qualcomm_atheros-' \
@ -233,7 +229,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
assert em.BenchmarkRamSysbench.t in action_types assert em.BenchmarkRamSysbench.t in action_types
assert em.StressTest.t in action_types assert em.StressTest.t in action_types
assert em.Snapshot.t in action_types assert em.Snapshot.t in action_types
assert len(actions) == 7 assert len(actions) == 8
gpu = components[3] gpu = components[3]
assert gpu['model'] == 'atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller' assert gpu['model'] == 'atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller'
assert gpu['manufacturer'] == 'intel corporation' assert gpu['manufacturer'] == 'intel corporation'
@ -243,8 +239,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
assert em.BenchmarkRamSysbench.t in action_types assert em.BenchmarkRamSysbench.t in action_types
assert em.StressTest.t in action_types assert em.StressTest.t in action_types
assert em.Snapshot.t in action_types assert em.Snapshot.t in action_types
# todo why?? change action types 3 to 5 assert len(action_types) == 6
assert len(action_types) == 5
sound = components[4] sound = components[4]
assert sound['model'] == 'nm10/ich7 family high definition audio controller' assert sound['model'] == 'nm10/ich7 family high definition audio controller'
sound = components[5] sound = components[5]
@ -266,8 +261,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
assert em.TestDataStorage.t in action_types assert em.TestDataStorage.t in action_types
assert em.EraseBasic.t in action_types assert em.EraseBasic.t in action_types
assert em.Snapshot.t in action_types assert em.Snapshot.t in action_types
# todo why?? change action types 6 to 8 assert len(action_types) == 9
assert len(action_types) == 8
erase = next(e for e in hdd['actions'] if e['type'] == em.EraseBasic.t) erase = next(e for e in hdd['actions'] if e['type'] == em.EraseBasic.t)
assert erase['endTime'] assert erase['endTime']
assert erase['startTime'] assert erase['startTime']
@ -277,17 +271,20 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
assert mother['hid'] == 'motherboard-asustek_computer_inc-1001pxd-eee0123456789' assert mother['hid'] == 'motherboard-asustek_computer_inc-1001pxd-eee0123456789'
@pytest.mark.mvp
def test_real_custom(user: UserClient): def test_real_custom(user: UserClient):
s = file('real-custom.snapshot.11') s = file('real-custom.snapshot.11')
snapshot, _ = user.post(res=em.Snapshot, data=s, status=NeedsId) snapshot, _ = user.post(res=em.Snapshot, data=s, status=NeedsId)
# todo insert with tag # todo insert with tag
@pytest.mark.mvp
def test_real_hp_quad_core(user: UserClient): def test_real_hp_quad_core(user: UserClient):
s = file('real-hp-quad-core.snapshot.11') s = file('real-hp-quad-core.snapshot.11')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
@pytest.mark.mvp
def test_real_eee_1000h(user: UserClient): def test_real_eee_1000h(user: UserClient):
s = file('asus-eee-1000h.snapshot.11') s = file('asus-eee-1000h.snapshot.11')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
@ -305,6 +302,7 @@ SNAPSHOTS_NEED_ID = {
"""Snapshots that do not generate HID requiring a custom ID.""" """Snapshots that do not generate HID requiring a custom ID."""
@pytest.mark.xfail(reason='It needs to be fixed.')
@pytest.mark.parametrize('file', @pytest.mark.parametrize('file',
(pytest.param(f, id=f.name) (pytest.param(f, id=f.name)
for f in pathlib.Path(__file__).parent.joinpath('workbench_files').iterdir()) for f in pathlib.Path(__file__).parent.joinpath('workbench_files').iterdir())
@ -320,12 +318,14 @@ def test_workbench_fixtures(file: pathlib.Path, user: UserClient):
status=201 if file.name not in SNAPSHOTS_NEED_ID else NeedsId) status=201 if file.name not in SNAPSHOTS_NEED_ID else NeedsId)
@pytest.mark.mvp
def test_workbench_asus_1001pxd_rate_low(user: UserClient): def test_workbench_asus_1001pxd_rate_low(user: UserClient):
"""Tests an Asus 1001pxd with a low rate.""" """Tests an Asus 1001pxd with a low rate."""
s = file('asus-1001pxd.snapshot') s = file('asus-1001pxd.snapshot')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)
@pytest.mark.mvp
def test_david(user: UserClient): def test_david(user: UserClient):
s = file('david.lshw.snapshot') s = file('david.lshw.snapshot')
snapshot, _ = user.post(res=em.Snapshot, data=s) snapshot, _ = user.post(res=em.Snapshot, data=s)