Add ComputerAccessory, Networking, Printer, Sound, Video devices

This commit is contained in:
Xavier Bustamante Talavera 2018-10-23 15:37:37 +02:00
parent 46f765a683
commit 10c73a4e75
11 changed files with 582 additions and 127 deletions

View file

@ -0,0 +1,14 @@
type: Snapshot
version: '1.0'
software: Web
device:
type: Keyboard
model: FOO
serialNumber: BAR
manufacturer: BAZ
layout: ES
events:
- type: ManualRate
appearanceRange: A
functionalityRange: A
labelling: False

View file

@ -161,6 +161,121 @@ class DisplayDef(ComponentDef):
SCHEMA = schemas.Display
class ComputerAccessoryDef(DeviceDef):
VIEW = None
SCHEMA = schemas.ComputerAccessory
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class MouseDef(ComputerAccessoryDef):
VIEW = None
SCHEMA = schemas.Mouse
class KeyboardDef(ComputerAccessoryDef):
VIEW = None
SCHEMA = schemas.Keyboard
class SAIDef(ComputerAccessoryDef):
VIEW = None
SCHEMA = schemas.SAI
class MemoryCardReaderDef(ComputerAccessoryDef):
VIEW = None
SCHEMA = schemas.MemoryCardReader
class NetworkingDef(DeviceDef):
VIEW = None
SCHEMA = schemas.Networking
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class RouterDef(NetworkingDef):
VIEW = None
SCHEMA = schemas.Router
class SwitchDef(NetworkingDef):
VIEW = None
SCHEMA = schemas.Switch
class HubDef(NetworkingDef):
VIEW = None
SCHEMA = schemas.Hub
class WirelessAccessPointDef(NetworkingDef):
VIEW = None
SCHEMA = schemas.WirelessAccessPoint
class PrinterDef(DeviceDef):
VIEW = None
SCHEMA = schemas.Printer
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class LabelPrinterDef(PrinterDef):
VIEW = None
SCHEMA = schemas.LabelPrinter
class SoundDef(DeviceDef):
VIEW = None
SCHEMA = schemas.Sound
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class MicrophoneDef(SoundDef):
VIEW = None
SCHEMA = schemas.Microphone
class VideoDef(DeviceDef):
VIEW = None
SCHEMA = schemas.Video
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class VideoScalerDef(VideoDef):
VIEW = None
SCHEMA = schemas.VideoScaler
class VideoconferenceDef(VideoDef):
VIEW = None
SCHEMA = schemas.Videoconference
class ManufacturerDef(Resource):
VIEW = ManufacturerView
SCHEMA = schemas.Manufacturer

View file

@ -17,12 +17,13 @@ from sqlalchemy_utils import ColorType
from stdnum import imei, meid
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, check_lower, \
check_range
from teal.enums import Layouts
from teal.marshmallow import ValidationError
from teal.resource import url_for_resource
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
DataStoragePrivacyCompliance, DisplayTech, PrinterTechnology, RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
@ -62,6 +63,19 @@ class Device(Thing):
"""
color = Column(ColorType)
color.comment = """The predominant color of the device."""
production_date = Column(db.TIMESTAMP(timezone=True))
production_date.comment = """The date of production of the item."""
_NON_PHYSICAL_PROPS = {
'id',
'type',
'created',
'updated',
'parent_id',
'hid',
'production_date',
'color'
}
@property
def events(self) -> list:
@ -94,7 +108,7 @@ class Device(Thing):
for c in inspect(self.__class__).attrs
if isinstance(c, ColumnProperty)
and not getattr(c, 'foreign_keys', None)
and c.key not in {'id', 'type', 'created', 'updated', 'parent_id', 'hid'}}
and c.key not in self._NON_PHYSICAL_PROPS}
@property
def url(self) -> urlutils.URL:
@ -194,6 +208,11 @@ class Device(Thing):
class DisplayMixin:
"""
Aspect ratio can be computed as in
https://github.com/mirukan/whratio/blob/master/whratio/ratio.py and
could be a future property.
"""
size = Column(Float(decimal_return_scale=2), check_range('size', 2, 150))
size.comment = """
The size of the monitor in inches.
@ -212,6 +231,10 @@ class DisplayMixin:
The maximum vertical resolution the monitor can natively support
in pixels.
"""
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
touchable = Column(Boolean, nullable=False, default=False)
touchable.comment = """Whether it is a touchscreen."""
def __format__(self, format_spec: str) -> str:
v = ''
@ -289,7 +312,9 @@ class Desktop(Computer):
class Laptop(Computer):
pass
layout = Column(DBEnum(Layouts))
layout.comment = """Layout of a built-in keyboard of the computer,
if any."""
class Server(Computer):
@ -308,6 +333,10 @@ class TelevisionSet(Monitor):
pass
class Projector(Monitor):
pass
class Mobile(Device):
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
imei = Column(BigInteger)
@ -483,6 +512,83 @@ class Display(JoinedComponentTableMixin, DisplayMixin, Component):
pass
class ComputerAccessory(Device):
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
pass
class SAI(ComputerAccessory):
pass
class Keyboard(ComputerAccessory):
layout = Column(DBEnum(Layouts)) # If we want to do it not null
class Mouse(ComputerAccessory):
pass
class MemoryCardReader(ComputerAccessory):
pass
class Networking(NetworkMixin, Device):
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
class Router(Networking):
pass
class Switch(Networking):
pass
class Hub(Networking):
pass
class WirelessAccessPoint(Networking):
pass
class Printer(Device):
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
wireless = Column(Boolean, nullable=False, default=False)
wireless.comment = """Whether it is a wireless printer."""
scanning = Column(Boolean, nullable=False, default=False)
scanning.comment = """Whether the printer has scanning capabilities."""
technology = Column(DBEnum(PrinterTechnology))
technology.comment = """Technology used to print."""
monochrome = Column(Boolean, nullable=False, default=True)
monochrome.comment = """Whether the printer is only monochrome."""
class LabelPrinter(Printer):
pass
class Sound(Device):
pass
class Microphone(Sound):
pass
class Video(Device):
pass
class VideoScaler(Video):
pass
class Videoconference(Video):
pass
class Manufacturer(db.Model):
__table_args__ = {'schema': 'common'}
CSV_DELIMITER = csv.get_dialect('excel').delimiter

View file

@ -1,3 +1,4 @@
from datetime import datetime
from typing import Dict, List, Set, Type, Union
from boltons import urlutils
@ -6,11 +7,12 @@ from colour import Color
from sqlalchemy import Column, Integer
from sqlalchemy.orm import relationship
from teal.db import Model
from teal.enums import Layouts
from ereuse_devicehub.resources.agent.models import Agent
from ereuse_devicehub.resources.device import states
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
DataStoragePrivacyCompliance, DisplayTech, PrinterTechnology, RamFormat, RamInterface
from ereuse_devicehub.resources.event import models as e
from ereuse_devicehub.resources.image.models import ImageList
from ereuse_devicehub.resources.lot.models import Lot
@ -31,6 +33,7 @@ class Device(Thing):
depth = ... # type: Column
color = ... # type: Column
lots = ... # type: relationship
production_date = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
@ -52,6 +55,7 @@ class Device(Thing):
self.images = ... # type: ImageList
self.tags = ... # type: Set[Tag]
self.lots = ... # type: Set[Lot]
self.production_date = ... # type: datetime
@property
def url(self) -> urlutils.URL:
@ -86,6 +90,9 @@ class DisplayMixin:
size = ... # type: Column
resolution_width = ... # type: Column
resolution_height = ... # type: Column
refresh_rate = ... # type: Column
contrast_ratio = ... # type: Column
touchable = ... # type: Column
def __init__(self) -> None:
super().__init__()
@ -93,6 +100,9 @@ class DisplayMixin:
self.size = ... # type: Integer
self.resolution_width = ... # type: int
self.resolution_height = ... # type: int
self.refresh_rate = ... # type: int
self.contrast_ratio = ... # type: int
self.touchable = ... # type: bool
class Computer(DisplayMixin, Device):
@ -135,7 +145,11 @@ class Desktop(Computer):
class Laptop(Computer):
pass
layout = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.layout = ... # type: Layouts
class Server(Computer):
@ -233,12 +247,18 @@ class Motherboard(Component):
self.pcmcia = ... # type: int
class NetworkAdapter(Component):
class NetworkMixin:
speed = ... # type: Column
wireless = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.speed = ... # type: int
self.wireless = ... # type: bool
class NetworkAdapter(NetworkMixin, Component):
pass
class Processor(Component):
@ -271,6 +291,88 @@ class Display(DisplayMixin, Component):
pass
class ComputerAccessory(Device):
pass
class SAI(ComputerAccessory):
pass
class Keyboard(ComputerAccessory):
layout = ... # type: Column
def __init__(self, layout: Layouts, **kwargs):
super().__init__(**kwargs)
self.layout = ... # type: Layouts
class Mouse(ComputerAccessory):
pass
class MemoryCardReader(ComputerAccessory):
pass
class Networking(NetworkMixin, Device):
pass
class Router(Networking):
pass
class Switch(Networking):
pass
class Hub(Networking):
pass
class WirelessAccessPoint(Networking):
pass
class Printer(Device):
wireless = ... # type: Column
scanning = ... # type: Column
technology = ... # type: Column
monochrome = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.wireless = ... # type: bool
self.scanning = ... # type: bool
self.technology = ... # type: PrinterTechnology
self.monochrome = ... # type: bool
class LabelPrinter(Printer):
pass
class Sound(Device):
pass
class Microphone(Sound):
pass
class Video(Device):
pass
class VideoScaler(Video):
pass
class Videoconference(Video):
pass
class Manufacturer(Model):
CUSTOM_MANUFACTURERS = ... # type: set
name = ... # type: Column

View file

@ -1,15 +1,16 @@
from marshmallow import post_load, pre_load
from marshmallow.fields import Boolean, Float, Integer, List, Str, String
from marshmallow.fields import Boolean, DateTime, Float, Integer, List, Str, String
from marshmallow.validate import Length, OneOf, Range
from sqlalchemy.util import OrderedSet
from stdnum import imei, meid
from teal.enums import Layouts
from teal.marshmallow import EnumField, SanitizedStr, URL, ValidationError
from teal.resource import Schema
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device import models as m, states
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
DataStoragePrivacyCompliance, DisplayTech, PrinterTechnology, RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
@ -40,6 +41,9 @@ class Device(Thing):
trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__)
physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__)
physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
production_date = DateTime('iso',
description=m.Device.updated.comment,
data_key='productionDate')
@pre_load
def from_events_to_events_one(self, data: dict):
@ -98,6 +102,9 @@ class DisplayMixin:
resolution_height = Integer(data_key='resolutionHeight',
validate=Range(10, 20000),
description=m.DisplayMixin.resolution_height.comment)
refresh_rate = Integer(data_key='refreshRate', validate=Range(10, 1000))
contrast_ratio = Integer(data_key='contrastRatio', validate=Range(100, 100000))
touchable = Boolean(missing=False, description=m.DisplayMixin.touchable.comment)
class NetworkMixin:
@ -212,3 +219,74 @@ class Manufacturer(Schema):
name = String(dump_only=True)
url = URL(dump_only=True)
logo = URL(dump_only=True)
class ComputerAccessory(Device):
pass
class Mouse(ComputerAccessory):
pass
class MemoryCardReader(ComputerAccessory):
pass
class SAI(ComputerAccessory):
pass
class Keyboard(ComputerAccessory):
layout = EnumField(Layouts)
class Networking(NetworkMixin, Device):
pass
class Router(Networking):
pass
class Switch(Networking):
pass
class Hub(Networking):
pass
class WirelessAccessPoint(Networking):
pass
class Printer(Device):
wireless = Boolean(required=True, missing=False)
scanning = Boolean(required=True, missing=False)
technology = EnumField(PrinterTechnology, required=True)
monochrome = Boolean(required=True, missing=True)
class LabelPrinter(Printer):
pass
class Sound(Device):
pass
class Microphone(Sound):
pass
class Video(Device):
pass
class VideoScaler(Video):
pass
class Videoconference(Video):
pass

View file

@ -276,3 +276,12 @@ class DataStoragePrivacyCompliance(Enum):
return cls.EraseSectors if not erasure.error else cls.EraseSectorsError
else:
return cls.EraseBasic if not erasure.error else cls.EraseBasicError
class PrinterTechnology(Enum):
"""Technology of the printer."""
Toner = 'Toner / Laser'
Inkjet = 'Liquid inkjet'
SolidInk = 'Solid ink'
Dye = 'Dye-sublimation'
Thermal = 'Thermal'

View file

@ -1,5 +1,6 @@
from sqlalchemy import Column
from datetime import datetime
from sqlalchemy import Column
from teal.db import Model
STR_SIZE = 64
@ -13,3 +14,8 @@ class Thing(Model):
type = ... # type: str
updated = ... # type: Column
created = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.updated = ... # type: datetime
self.created = ... # type: datetime

View file

@ -22,8 +22,8 @@ class UnitCodes(Enum):
class Thing(Schema):
type = String(description='Only required when it is nested.')
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment.strip())
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment.strip())
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment)
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment)
@post_load
def remove_type(self, data: dict):

View file

@ -40,4 +40,4 @@ def test_api_docs(client: Client):
'scheme': 'basic',
'name': 'Authorization'
}
assert 75 == len(docs['definitions'])
assert 92 == len(docs['definitions'])

View file

@ -9,14 +9,14 @@ from ereuse_utils.test import ANY
from pytest import raises
from sqlalchemy.util import OrderedSet
from teal.db import ResourceNotFound
from teal.enums import Layouts
from ereuse_devicehub.client import Client, UserClient
from ereuse_devicehub.db import db
from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.resources.agent.models import Person
from ereuse_devicehub.resources.device import models as d
from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.models import Component, ComputerMonitor, DataStorage, \
Desktop, Device, GraphicCard, Laptop, Motherboard, NetworkAdapter
from ereuse_devicehub.resources.device.schemas import Device as DeviceS
from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, MismatchBetweenTagsAndHid, \
Sync
@ -34,43 +34,43 @@ def test_device_model():
"""
Tests that the correctness of the device model and its relationships.
"""
pc = Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
net = NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s')
graphic = GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
pc = d.Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
net = d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s')
graphic = d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
pc.components.add(net)
pc.components.add(graphic)
db.session.add(pc)
db.session.commit()
pc = Desktop.query.one()
pc = d.Desktop.query.one()
assert pc.serial_number == 'p1s'
assert pc.components == OrderedSet([net, graphic])
network_adapter = NetworkAdapter.query.one()
network_adapter = d.NetworkAdapter.query.one()
assert network_adapter.parent == pc
# Removing a component from pc doesn't delete the component
pc.components.remove(net)
db.session.commit()
pc = Device.query.first() # this is the same as querying for Desktop directly
pc = d.Device.query.first() # this is the same as querying for d.Desktop directly
assert pc.components == {graphic}
network_adapter = NetworkAdapter.query.one()
network_adapter = d.NetworkAdapter.query.one()
assert network_adapter not in pc.components
assert network_adapter.parent is None
# Deleting the pc deletes everything
gcard = GraphicCard.query.one()
gcard = d.GraphicCard.query.one()
db.session.delete(pc)
db.session.flush()
assert pc.id == 1
assert Desktop.query.first() is None
assert d.Desktop.query.first() is None
db.session.commit()
assert Desktop.query.first() is None
assert d.Desktop.query.first() is None
assert network_adapter.id == 2
assert NetworkAdapter.query.first() is not None, 'We removed the network adaptor'
assert d.NetworkAdapter.query.first() is not None, 'We removed the network adaptor'
assert gcard.id == 3, 'We should still hold a reference to a zombie graphic card'
assert GraphicCard.query.first() is None, 'We should have deleted it it was inside the pc'
assert d.GraphicCard.query.first() is None, 'We should have deleted it it was inside the pc'
@pytest.mark.usefixtures(conftest.app_context.__name__)
@ -78,26 +78,26 @@ def test_device_schema():
"""Ensures the user does not upload non-writable or extra fields."""
device_s = DeviceS()
device_s.load({'serialNumber': 'foo1', 'model': 'foo', 'manufacturer': 'bar2'})
device_s.dump(Device(id=1))
device_s.dump(d.Device(id=1))
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_physical_properties():
c = Motherboard(slots=2,
usb=3,
serial_number='sn',
model='ml',
manufacturer='mr',
width=2.0,
color=Color())
pc = Desktop(chassis=ComputerChassis.Tower,
model='foo',
manufacturer='bar',
serial_number='foo-bar',
weight=2.8,
width=1.4,
height=2.1,
color=Color('LightSeaGreen'))
c = d.Motherboard(slots=2,
usb=3,
serial_number='sn',
model='ml',
manufacturer='mr',
width=2.0,
color=Color())
pc = d.Desktop(chassis=ComputerChassis.Tower,
model='foo',
manufacturer='bar',
serial_number='foo-bar',
weight=2.8,
width=1.4,
height=2.1,
color=Color('LightSeaGreen'))
pc.components.add(c)
db.session.add(pc)
db.session.commit()
@ -113,7 +113,6 @@ def test_physical_properties():
'weight': None,
'height': None,
'width': 2.0,
'color': Color(),
'depth': None
}
assert pc.physical_properties == {
@ -124,7 +123,6 @@ def test_physical_properties():
'width': 1.4,
'height': 2.1,
'depth': None,
'color': Color('LightSeaGreen'),
'chassis': ComputerChassis.Tower
}
@ -132,18 +130,18 @@ def test_physical_properties():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_component_similar_one():
snapshot = conftest.file('pc-components.db')
d = snapshot['device']
pc = snapshot['device']
snapshot['components'][0]['serial_number'] = snapshot['components'][1]['serial_number'] = None
pc = Desktop(**d, components=OrderedSet(Component(**c) for c in snapshot['components']))
component1, component2 = pc.components # type: Component
pc = d.Desktop(**pc, components=OrderedSet(d.Component(**c) for c in snapshot['components']))
component1, component2 = pc.components # type: d.Component
db.session.add(pc)
db.session.flush()
# Let's create a new component named 'A' similar to 1
componentA = Component(model=component1.model, manufacturer=component1.manufacturer)
componentA = d.Component(model=component1.model, manufacturer=component1.manufacturer)
similar_to_a = componentA.similar_one(pc, set())
assert similar_to_a == component1
# Component B does not have the same model
componentB = Component(model='nope', manufacturer=component1.manufacturer)
# d.Component B does not have the same model
componentB = d.Component(model='nope', manufacturer=component1.manufacturer)
with pytest.raises(ResourceNotFound):
assert componentB.similar_one(pc, set())
# If we blacklist component A we won't get anything
@ -159,14 +157,14 @@ def test_add_remove():
# c4 is not with any pc
values = conftest.file('pc-components.db')
pc = values['device']
c1, c2 = (Component(**c) for c in values['components'])
pc = Desktop(**pc, components=OrderedSet([c1, c2]))
c1, c2 = (d.Component(**c) for c in values['components'])
pc = d.Desktop(**pc, components=OrderedSet([c1, c2]))
db.session.add(pc)
c3 = Component(serial_number='nc1')
pc2 = Desktop(serial_number='s2',
components=OrderedSet([c3]),
chassis=ComputerChassis.Microtower)
c4 = Component(serial_number='c4s')
c3 = d.Component(serial_number='nc1')
pc2 = d.Desktop(serial_number='s2',
components=OrderedSet([c3]),
chassis=ComputerChassis.Microtower)
c4 = d.Component(serial_number='c4s')
db.session.add(pc2)
db.session.add(c4)
db.session.commit()
@ -189,12 +187,12 @@ def test_sync_run_components_empty():
remove all the components from the device.
"""
s = conftest.file('pc-components.db')
pc = Desktop(**s['device'], components=OrderedSet(Component(**c) for c in s['components']))
pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components']))
db.session.add(pc)
db.session.commit()
# Create a new transient non-db synced object
pc = Desktop(**s['device'])
pc = d.Desktop(**s['device'])
db_pc, _ = Sync().run(pc, components=OrderedSet())
assert not db_pc.components
assert not pc.components
@ -207,25 +205,25 @@ def test_sync_run_components_none():
keep all the components from the device.
"""
s = conftest.file('pc-components.db')
pc = Desktop(**s['device'], components=OrderedSet(Component(**c) for c in s['components']))
pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components']))
db.session.add(pc)
db.session.commit()
# Create a new transient non-db synced object
transient_pc = Desktop(**s['device'])
transient_pc = d.Desktop(**s['device'])
db_pc, _ = Sync().run(transient_pc, components=None)
assert db_pc.components
assert db_pc.components == pc.components
@pytest.mark.usefixtures(conftest.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 Desktop with HID and without a tag, creating it.
Syncs a new d.Desktop with HID and without a tag, creating it.
:return:
"""
# Case 1: device does not exist on DB
pc = Desktop(**conftest.file('pc-components.db')['device'])
pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db_pc = Sync().execute_register(pc)
assert pc.physical_properties == db_pc.physical_properties
@ -233,13 +231,13 @@ def test_sync_execute_register_desktop_new_Desktop_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_existing_no_tag():
"""
Syncs an existing Desktop with HID and without a tag.
Syncs an existing d.Desktop with HID and without a tag.
"""
pc = Desktop(**conftest.file('pc-components.db')['device'])
pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db.session.add(pc)
db.session.commit()
pc = Desktop(
pc = d.Desktop(
**conftest.file('pc-components.db')['device']) # Create a new transient non-db object
# 1: device exists on DB
db_pc = Sync().execute_register(pc)
@ -249,11 +247,11 @@ def test_sync_execute_register_desktop_existing_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_no_hid_no_tag():
"""
Syncs a Desktop without HID and no tag.
Syncs a d.Desktop without HID and no tag.
This should fail as we don't have a way to identify it.
"""
pc = Desktop(**conftest.file('pc-components.db')['device'])
pc = d.Desktop(**conftest.file('pc-components.db')['device'])
# 1: device has no HID
pc.hid = pc.model = None
with pytest.raises(NeedsId):
@ -263,7 +261,7 @@ def test_sync_execute_register_desktop_no_hid_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_tag_not_linked():
"""
Syncs a new Desktop with HID and a non-linked tag.
Syncs a new d.Desktop with HID and a non-linked tag.
It is OK if the tag was not linked, it will be linked in this process.
"""
@ -272,24 +270,24 @@ def test_sync_execute_register_desktop_tag_not_linked():
db.session.commit()
# Create a new transient non-db object
pc = Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')]))
pc = d.Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')]))
returned_pc = Sync().execute_register(pc)
assert returned_pc == pc
assert tag.device == pc, 'Tag has to be linked'
assert Desktop.query.one() == pc, '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__)
def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
"""
Validates registering a Desktop without HID and a non-linked tag.
Validates registering a d.Desktop without HID and a non-linked tag.
In this case it is ok still, as the non-linked tag proves that
the Desktop was not existing before (otherwise the tag would
be linked), and thus it creates a new Desktop.
the d.Desktop was not existing before (otherwise the tag would
be linked), and thus it creates a new d.Desktop.
"""
tag = Tag(id=tag_id)
pc = Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([tag]))
pc = d.Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([tag]))
returned_pc = Sync().execute_register(pc)
db.session.commit()
assert returned_pc == pc
@ -299,7 +297,7 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
# they have the same pk though
assert tag != db_tag, 'They are not the same tags though'
assert db_tag.id == tag.id
assert Desktop.query.one() == pc, '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__)
@ -310,7 +308,7 @@ def test_sync_execute_register_tag_does_not_exist():
Tags have to be created before trying to link them through a Snapshot.
"""
pc = Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([Tag('foo')]))
pc = d.Desktop(**conftest.file('pc-components.db')['device'], tags=OrderedSet([Tag('foo')]))
with raises(ResourceNotFound):
Sync().execute_register(pc)
@ -323,11 +321,11 @@ def test_sync_execute_register_tag_linked_same_device():
(If it has HID it validates both HID and tag point at the same
device, this his checked in ).
"""
orig_pc = Desktop(**conftest.file('pc-components.db')['device'])
orig_pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db.session.add(Tag(id='foo', device=orig_pc))
db.session.commit()
pc = Desktop(
pc = d.Desktop(
**conftest.file('pc-components.db')['device']) # Create a new transient non-db object
pc.tags.add(Tag(id='foo'))
db_pc = Sync().execute_register(pc)
@ -342,15 +340,15 @@ 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
tags are not linked to the same device.
"""
pc1 = Desktop(**conftest.file('pc-components.db')['device'])
pc1 = d.Desktop(**conftest.file('pc-components.db')['device'])
db.session.add(Tag(id='foo-1', device=pc1))
pc2 = Desktop(**conftest.file('pc-components.db')['device'])
pc2 = d.Desktop(**conftest.file('pc-components.db')['device'])
pc2.serial_number = 'pc2-serial'
pc2.hid = Naming.hid(pc2.manufacturer, pc2.serial_number, pc2.model)
db.session.add(Tag(id='foo-2', device=pc2))
db.session.commit()
pc1 = Desktop(
pc1 = d.Desktop(
**conftest.file('pc-components.db')['device']) # Create a new transient non-db object
pc1.tags.add(Tag(id='foo-1'))
pc1.tags.add(Tag(id='foo-2'))
@ -366,15 +364,15 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
In this case we set HID -> pc1 but tag -> pc2
"""
pc1 = Desktop(**conftest.file('pc-components.db')['device'])
pc1 = d.Desktop(**conftest.file('pc-components.db')['device'])
db.session.add(Tag(id='foo-1', device=pc1))
pc2 = Desktop(**conftest.file('pc-components.db')['device'])
pc2 = d.Desktop(**conftest.file('pc-components.db')['device'])
pc2.serial_number = 'pc2-serial'
pc2.hid = Naming.hid(pc2.manufacturer, pc2.serial_number, pc2.model)
db.session.add(Tag(id='foo-2', device=pc2))
db.session.commit()
pc1 = Desktop(
pc1 = d.Desktop(
**conftest.file('pc-components.db')['device']) # Create a new transient non-db object
pc1.tags.add(Tag(id='foo-2'))
with raises(MismatchBetweenTagsAndHid):
@ -382,15 +380,15 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
def test_get_device(app: Devicehub, user: UserClient):
"""Checks GETting a Desktop with its components."""
"""Checks GETting a d.Desktop with its components."""
with app.app_context():
pc = Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
pc = d.Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
pc.components = OrderedSet([
NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s'),
GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s'),
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
])
db.session.add(pc)
db.session.add(Test(device=pc,
@ -399,7 +397,7 @@ def test_get_device(app: Devicehub, user: UserClient):
agent=Person(name='Timmy'),
author=User(email='bar@bar.com')))
db.session.commit()
pc, _ = user.get(res=Device, item=1)
pc, _ = user.get(res=d.Device, item=1)
assert len(pc['events']) == 1
assert pc['events'][0]['type'] == 'Test'
assert pc['events'][0]['device'] == 1
@ -414,46 +412,46 @@ def test_get_device(app: Devicehub, user: UserClient):
assert pc['model'] == 'p1mo'
assert pc['manufacturer'] == 'p1ma'
assert pc['serialNumber'] == 'p1s'
assert pc['type'] == 'Desktop'
assert pc['type'] == d.Desktop.t
def test_get_devices(app: Devicehub, user: UserClient):
"""Checks GETting multiple devices."""
with app.app_context():
pc = Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
pc = d.Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
pc.components = OrderedSet([
NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s'),
GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s'),
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
])
pc1 = Desktop(model='p2mo',
manufacturer='p2ma',
serial_number='p2s',
chassis=ComputerChassis.Tower)
pc2 = Laptop(model='p3mo',
manufacturer='p3ma',
serial_number='p3s',
chassis=ComputerChassis.Netbook)
pc1 = d.Desktop(model='p2mo',
manufacturer='p2ma',
serial_number='p2s',
chassis=ComputerChassis.Tower)
pc2 = d.Laptop(model='p3mo',
manufacturer='p3ma',
serial_number='p3s',
chassis=ComputerChassis.Netbook)
db.session.add_all((pc, pc1, pc2))
db.session.commit()
devices, _ = user.get(res=Device)
assert tuple(d['id'] for d in devices['items']) == (1, 2, 3, 4, 5)
assert tuple(d['type'] for d in devices['items']) == (
'Desktop', 'Desktop', 'Laptop', 'NetworkAdapter', 'GraphicCard'
devices, _ = user.get(res=d.Device)
assert tuple(dev['id'] for dev in devices['items']) == (1, 2, 3, 4, 5)
assert tuple(dev['type'] for dev in devices['items']) == (
d.Desktop.t, d.Desktop.t, d.Laptop.t, d.NetworkAdapter.t, d.GraphicCard.t
)
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_computer_monitor():
m = ComputerMonitor(technology=DisplayTech.LCD,
manufacturer='foo',
model='bar',
serial_number='foo-bar',
resolution_width=1920,
resolution_height=1080,
size=14.5)
m = d.ComputerMonitor(technology=DisplayTech.LCD,
manufacturer='foo',
model='bar',
serial_number='foo-bar',
resolution_width=1920,
resolution_height=1080,
size=14.5)
db.session.add(m)
db.session.commit()
@ -489,7 +487,7 @@ def test_manufacturer_enforced():
def test_device_properties_format(app: Devicehub, user: UserClient):
user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
with app.app_context():
pc = Laptop.query.one() # type: Laptop
pc = d.Laptop.query.one() # type: d.Laptop
assert format(pc) == 'Laptop 1: model 1000h, S/N 94oaaq021116'
assert format(pc, 't') == 'Netbook 1000h'
assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116'
@ -497,12 +495,12 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
assert pc.data_storage_size == 152627
assert pc.graphic_card_model == 'mobile 945gse express integrated graphics controller'
assert pc.processor_model == 'intel atom cpu n270 @ 1.60ghz'
net = next(c for c in pc.components if isinstance(c, NetworkAdapter))
net = next(c for c in pc.components if isinstance(c, d.NetworkAdapter))
assert format(net) == 'NetworkAdapter 2: model ar8121/ar8113/ar8114 ' \
'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d'
assert format(net, 't') == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet'
assert format(net, 's') == '(qualcomm atheros) S/N 00:24:8C:7F:CF:2D 100 Mbps'
hdd = next(c for c in pc.components if isinstance(c, DataStorage))
hdd = next(c for c in pc.components if isinstance(c, d.DataStorage))
assert format(hdd) == 'HardDrive 7: model st9160310as, S/N 5sv4tqa6'
assert format(hdd, 't') == 'HardDrive st9160310as'
assert format(hdd, 's') == '(seagate) S/N 5SV4TQA6 152 GB'
@ -510,6 +508,26 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
def test_device_public(user: UserClient, client: Client):
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
html, _ = client.get(res=Device, item=s['device']['id'], accept=ANY)
html, _ = client.get(res=d.Device, item=s['device']['id'], accept=ANY)
assert 'intel atom cpu n270 @ 1.60ghz' in html
assert 'S/N 00:24:8C:7F:CF:2D 100 Mbps' in html
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_computer_accessory_model():
sai = d.SAI()
db.session.add(sai)
keyboard = d.Keyboard(layout=Layouts.ES)
db.session.add(keyboard)
mouse = d.Mouse()
db.session.add(mouse)
db.session.commit()
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_networking_model():
router = d.Router(speed=1000, wireless=True)
db.session.add(router)
switch = d.Switch(speed=1000, wireless=False)
db.session.add(switch)
db.session.commit()

View file

@ -411,3 +411,10 @@ def snapshot_and_check(user: UserClient,
return snapshot_and_check(user, input_snapshot, event_types, perform_second_snapshot=False)
else:
return snapshot
def test_snapshot_keyboard(user: UserClient):
s = file('keyboard.snapshot')
snapshot = snapshot_and_check(user, s, event_types=('ManualRate',))
keyboard = snapshot['device']
assert keyboard['layout'] == 'ES'