Merge remote-tracking branch 'origin/master' into reports
This commit is contained in:
commit
79f41f3501
|
@ -12,6 +12,7 @@ state Attributes {
|
|||
state Usufructuarees
|
||||
state Reservees
|
||||
state "Physical\nPossessor"
|
||||
state "Waste\n\Product"
|
||||
}
|
||||
|
||||
state Physical {
|
||||
|
@ -35,8 +36,17 @@ state Trading {
|
|||
Reserved --> Cancelled : Cancel
|
||||
Sold --> Cancelled : Cancel
|
||||
Sold --> Payed : Pay
|
||||
Registered --> ToBeDisposed
|
||||
ToBeDisposed --> Disposed : DisposeProduct
|
||||
Registered --> ToBeDisposed : ToDisposeProduct
|
||||
ToBeDisposed --> ProductDisposed : DisposeProduct
|
||||
Registered --> Donated: Donate
|
||||
Registered --> Renting: Rent
|
||||
Donated --> Cancelled : Cancel
|
||||
Renting --> Cancelled : Cancel
|
||||
}
|
||||
|
||||
state DataStoragePrivacyCompliance {
|
||||
state Erased
|
||||
state Destroyed
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ device:
|
|||
resolutionHeight: 1080
|
||||
size: 21.5
|
||||
events:
|
||||
- type: AppRate
|
||||
- type: ManualRate
|
||||
appearanceRange: A
|
||||
functionalityRange: C
|
||||
labelling: False
|
||||
|
|
|
@ -8,7 +8,7 @@ device:
|
|||
serialNumber: ABCDEF
|
||||
imei: 35686800-004141-20
|
||||
events:
|
||||
- type: AppRate
|
||||
- type: ManualRate
|
||||
appearanceRange: A
|
||||
functionalityRange: B
|
||||
labelling: False
|
||||
|
|
|
@ -26,6 +26,7 @@ device:
|
|||
functionalityRange: B
|
||||
- type: BenchmarkRamSysbench
|
||||
rate: 2444
|
||||
elapsed: 1
|
||||
components:
|
||||
- type: GraphicCard
|
||||
serialNumber: gc1-1s
|
||||
|
@ -35,22 +36,27 @@ components:
|
|||
serialNumber: rm1-1s
|
||||
model: rm1-1ml
|
||||
manufacturer: rm1-1mr
|
||||
size: 1024
|
||||
- type: RamModule
|
||||
serialNumber: rm2-1s
|
||||
model: rm2-1ml
|
||||
manufacturer: rm2-1mr
|
||||
size: 1024
|
||||
- type: Processor
|
||||
model: p1-1s
|
||||
model: p1-1ml
|
||||
manufacturer: p1-1mr
|
||||
events:
|
||||
- type: BenchmarkProcessor
|
||||
rate: 2410
|
||||
elapsed: 44
|
||||
- type: BenchmarkProcessorSysbench
|
||||
rate: 4400
|
||||
elapsed: 44
|
||||
- type: SolidStateDrive
|
||||
serialNumber: ssd1-1s
|
||||
model: ssd1-1ml
|
||||
manufacturer: ssd1-1mr
|
||||
size: 1100
|
||||
events:
|
||||
- type: BenchmarkDataStorage
|
||||
readSpeed: 20
|
||||
|
@ -78,7 +84,24 @@ components:
|
|||
- type: BenchmarkDataStorage
|
||||
readSpeed: 10
|
||||
writeSpeed: 5
|
||||
elapsed: 20
|
||||
- type: Motherboard
|
||||
serialNumber: mb1-1s
|
||||
model: mb1-1ml
|
||||
manufacturer: mb1-1mr
|
||||
- type: NetworkAdapter
|
||||
serialNumber: na1-s
|
||||
model: na1-1ml
|
||||
manufacturer: na1-1mr
|
||||
speed: 1000
|
||||
wireless: False
|
||||
- type: NetworkAdapter
|
||||
serialNumber: na2-s
|
||||
model: na2-1ml
|
||||
manufacturer: na2-1mr
|
||||
wireless: True
|
||||
speed: 58
|
||||
- type: RamModule
|
||||
serialNumber: rm3-1s
|
||||
model: rm3-1ml
|
||||
manufacturer: rm3-1mr
|
||||
|
|
|
@ -21,8 +21,8 @@ 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, DisplayTech, \
|
||||
RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
|
||||
DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||
|
||||
|
||||
|
@ -44,19 +44,22 @@ class Device(Thing):
|
|||
model = Column(Unicode(), check_lower('model'))
|
||||
manufacturer = Column(Unicode(), check_lower('manufacturer'))
|
||||
serial_number = Column(Unicode(), check_lower('serial_number'))
|
||||
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 3))
|
||||
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
|
||||
weight.comment = """
|
||||
The weight of the device in Kgm.
|
||||
"""
|
||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 3))
|
||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
||||
width.comment = """
|
||||
The width of the device in meters.
|
||||
"""
|
||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 3))
|
||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
||||
height.comment = """
|
||||
The height of the device in meters.
|
||||
"""
|
||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
|
||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
||||
depth.comment = """
|
||||
The depth of the device in meters.
|
||||
"""
|
||||
color = Column(ColorType)
|
||||
color.comment = """The predominant color of the device."""
|
||||
|
||||
|
@ -98,6 +101,55 @@ class Device(Thing):
|
|||
"""The URL where to GET this device."""
|
||||
return urlutils.URL(url_for_resource(Device, item_id=self.id))
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
"""The last AggregateRate of the device."""
|
||||
with suppress(LookupError, ValueError):
|
||||
from ereuse_devicehub.resources.event.models import AggregateRate
|
||||
return self.last_event_of(AggregateRate)
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
"""The actual Price of the device, or None if no price has
|
||||
ever been set."""
|
||||
with suppress(LookupError, ValueError):
|
||||
from ereuse_devicehub.resources.event.models import Price
|
||||
return self.last_event_of(Price)
|
||||
|
||||
@property
|
||||
def trading(self):
|
||||
"""The actual trading state, or None if no Trade event has
|
||||
ever been performed to this device."""
|
||||
from ereuse_devicehub.resources.device import states
|
||||
with suppress(LookupError, ValueError):
|
||||
event = self.last_event_of(*states.Trading.events())
|
||||
return states.Trading(event.__class__)
|
||||
|
||||
@property
|
||||
def physical(self):
|
||||
"""The actual physical state, None otherwise."""
|
||||
from ereuse_devicehub.resources.device import states
|
||||
with suppress(LookupError, ValueError):
|
||||
event = self.last_event_of(*states.Physical.events())
|
||||
return states.Physical(event.__class__)
|
||||
|
||||
@property
|
||||
def physical_possessor(self):
|
||||
"""The actual physical possessor or None.
|
||||
|
||||
The physical possessor is the Agent that has physically
|
||||
the device. It differs from legal owners, usufructuarees
|
||||
or reserves in that the physical possessor does not have
|
||||
a legal relation per se with the device, but it is the one
|
||||
that has it physically. As an example, a transporter could
|
||||
be a physical possessor of a device although it does not
|
||||
own it legally.
|
||||
"""
|
||||
from ereuse_devicehub.resources.event.models import Receive
|
||||
with suppress(LookupError):
|
||||
event = self.last_event_of(Receive)
|
||||
return event.agent
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
|
@ -112,6 +164,16 @@ class Device(Thing):
|
|||
args[POLYMORPHIC_ON] = cls.type
|
||||
return args
|
||||
|
||||
def last_event_of(self, *types):
|
||||
"""Gets the last event of the given types.
|
||||
|
||||
:raise LookupError: Device has not an event of the given type.
|
||||
"""
|
||||
try:
|
||||
return next(e for e in reversed(self.events) if isinstance(e, types))
|
||||
except StopIteration:
|
||||
raise LookupError('{!r} does not contain events of types {}.'.format(self, types))
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.id < other.id
|
||||
|
||||
|
@ -169,31 +231,38 @@ class Computer(Device):
|
|||
@property
|
||||
def ram_size(self) -> int:
|
||||
"""The total of RAM memory the computer has."""
|
||||
return sum(ram.size for ram in self.components if isinstance(ram, RamModule))
|
||||
return sum(ram.size or 0 for ram in self.components if isinstance(ram, RamModule))
|
||||
|
||||
@property
|
||||
def data_storage_size(self) -> int:
|
||||
"""The total of data storage the computer has."""
|
||||
return sum(ds.size for ds in self.components if isinstance(ds, DataStorage))
|
||||
return sum(ds.size or 0 for ds in self.components if isinstance(ds, DataStorage))
|
||||
|
||||
@property
|
||||
def processor_model(self) -> str:
|
||||
"""The model of one of the processors of the computer."""
|
||||
return next(p.model for p in self.components if isinstance(p, Processor))
|
||||
return next((p.model for p in self.components if isinstance(p, Processor)), None)
|
||||
|
||||
@property
|
||||
def graphic_card_model(self) -> str:
|
||||
"""The model of one of the graphic cards of the computer."""
|
||||
return next(p.model for p in self.components if isinstance(p, GraphicCard))
|
||||
return next((p.model for p in self.components if isinstance(p, GraphicCard)), None)
|
||||
|
||||
@property
|
||||
def network_speeds(self) -> List[int]:
|
||||
"""Returns two speeds: the first for the eth and the
|
||||
second for the wifi networks, or 0 respectively if not found.
|
||||
"""Returns two values representing the speeds of the network
|
||||
adapters of the device.
|
||||
|
||||
1. The max Ethernet speed of the computer, 0 if ethernet
|
||||
adaptor exists but its speed is unknown, None if no eth
|
||||
adaptor exists.
|
||||
2. The max WiFi speed of the computer, 0 if computer has
|
||||
WiFi but its speed is unknown, None if no WiFi adaptor
|
||||
exists.
|
||||
"""
|
||||
speeds = [0, 0]
|
||||
speeds = [None, None]
|
||||
for net in (c for c in self.components if isinstance(c, NetworkAdapter)):
|
||||
speeds[net.wireless] = max(net.speed or 0, speeds[net.wireless])
|
||||
speeds[net.wireless] = max(net.speed or 0, speeds[net.wireless] or 0)
|
||||
return speeds
|
||||
|
||||
def __format__(self, format_spec):
|
||||
|
@ -322,6 +391,15 @@ class DataStorage(JoinedComponentTableMixin, Component):
|
|||
"""
|
||||
interface = Column(DBEnum(DataStorageInterface))
|
||||
|
||||
@property
|
||||
def privacy(self):
|
||||
"""Returns the privacy compliance state of the data storage."""
|
||||
# todo add physical destruction event
|
||||
from ereuse_devicehub.resources.event.models import EraseBasic
|
||||
with suppress(LookupError):
|
||||
erase = self.last_event_of(EraseBasic)
|
||||
return DataStoragePrivacyCompliance.from_erase(erase)
|
||||
|
||||
def __format__(self, format_spec):
|
||||
v = super().__format__(format_spec)
|
||||
if 's' in format_spec:
|
||||
|
@ -353,7 +431,7 @@ class NetworkMixin:
|
|||
speed.comment = """
|
||||
The maximum speed this network adapter can handle, in mbps.
|
||||
"""
|
||||
wireless = Column(Boolean)
|
||||
wireless = Column(Boolean, nullable=False, default=False)
|
||||
wireless.comment = """
|
||||
Whether it is a wireless interface.
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List, Set
|
||||
from typing import Dict, List, Set, Type, Union
|
||||
|
||||
from boltons import urlutils
|
||||
from boltons.urlutils import URL
|
||||
|
@ -7,10 +7,11 @@ from sqlalchemy import Column, Integer
|
|||
from sqlalchemy.orm import relationship
|
||||
from teal.db import Model
|
||||
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
||||
RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.event.models import Event, EventWithMultipleDevices, \
|
||||
EventWithOneDevice
|
||||
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
|
||||
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
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
|
@ -44,10 +45,10 @@ class Device(Thing):
|
|||
self.height = ... # type: float
|
||||
self.depth = ... # type: float
|
||||
self.color = ... # type: Color
|
||||
self.events = ... # type: List[Event]
|
||||
self.events = ... # type: List[e.Event]
|
||||
self.physical_properties = ... # type: Dict[str, object or None]
|
||||
self.events_multiple = ... # type: Set[EventWithMultipleDevices]
|
||||
self.events_one = ... # type: Set[EventWithOneDevice]
|
||||
self.events_multiple = ... # type: Set[e.EventWithMultipleDevices]
|
||||
self.events_one = ... # type: Set[e.EventWithOneDevice]
|
||||
self.images = ... # type: ImageList
|
||||
self.tags = ... # type: Set[Tag]
|
||||
self.lots = ... # type: Set[Lot]
|
||||
|
@ -56,6 +57,30 @@ class Device(Thing):
|
|||
def url(self) -> urlutils.URL:
|
||||
pass
|
||||
|
||||
@property
|
||||
def rate(self) -> Union[e.AggregateRate, None]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def price(self) -> Union[e.Price, None]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def trading(self) -> Union[states.Trading, None]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def physical(self) -> Union[states.Physical, None]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def physical_possessor(self) -> Union[Agent, None]:
|
||||
pass
|
||||
|
||||
def last_event_of(self, *types: Type[e.Event]) -> e.Event:
|
||||
pass
|
||||
|
||||
|
||||
class DisplayMixin:
|
||||
technology = ... # type: Column
|
||||
size = ... # type: Column
|
||||
|
@ -77,7 +102,7 @@ class Computer(DisplayMixin, Device):
|
|||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.components = ... # type: Set[Component]
|
||||
self.events_parent = ... # type: Set[Event]
|
||||
self.events_parent = ... # type: Set[e.Event]
|
||||
self.chassis = ... # type: ComputerChassis
|
||||
|
||||
@property
|
||||
|
@ -104,6 +129,7 @@ class Computer(DisplayMixin, Device):
|
|||
def network_speeds(self) -> List[int]:
|
||||
pass
|
||||
|
||||
|
||||
class Desktop(Computer):
|
||||
pass
|
||||
|
||||
|
@ -155,7 +181,7 @@ class Component(Device):
|
|||
super().__init__(**kwargs)
|
||||
self.parent_id = ... # type: int
|
||||
self.parent = ... # type: Computer
|
||||
self.events_components = ... # type: Set[Event]
|
||||
self.events_components = ... # type: Set[e.Event]
|
||||
|
||||
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
||||
pass
|
||||
|
@ -178,6 +204,10 @@ class DataStorage(Component):
|
|||
self.size = ... # type: int
|
||||
self.interface = ... # type: DataStorageInterface
|
||||
|
||||
@property
|
||||
def privacy(self) -> DataStoragePrivacyCompliance:
|
||||
pass
|
||||
|
||||
|
||||
class HardDrive(DataStorage):
|
||||
pass
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from marshmallow import post_load, pre_load
|
||||
from marshmallow.fields import Boolean, Float, Integer, Str, String
|
||||
from marshmallow.fields import Boolean, Float, Integer, List, Str, String
|
||||
from marshmallow.validate import Length, OneOf, Range
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from stdnum import imei, meid
|
||||
|
@ -7,9 +7,9 @@ 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
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
||||
RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.device import models as m, states
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, \
|
||||
DataStoragePrivacyCompliance, DisplayTech, RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
|
||||
|
||||
|
@ -24,13 +24,22 @@ class Device(Thing):
|
|||
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
|
||||
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
||||
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
||||
weight = Float(validate=Range(0.1, 3), unit=UnitCodes.kgm, description=m.Device.weight.comment)
|
||||
width = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.width.comment)
|
||||
height = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.height.comment)
|
||||
weight = Float(validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment)
|
||||
width = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment)
|
||||
height = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment)
|
||||
depth = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.depth.comment)
|
||||
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
|
||||
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
||||
url = URL(dump_only=True, description=m.Device.url.__doc__)
|
||||
lots = NestedOn('Lot', many=True, dump_only=True)
|
||||
lots = NestedOn('Lot',
|
||||
many=True,
|
||||
dump_only=True,
|
||||
description='The lots where this device is directly under.')
|
||||
rate = NestedOn('AggregateRate', dump_only=True, description=m.Device.rate.__doc__)
|
||||
price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__)
|
||||
trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__)
|
||||
physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__)
|
||||
physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
|
||||
|
||||
@pre_load
|
||||
def from_events_to_events_one(self, data: dict):
|
||||
|
@ -60,6 +69,11 @@ class Device(Thing):
|
|||
class Computer(Device):
|
||||
components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet)
|
||||
chassis = EnumField(ComputerChassis, required=True)
|
||||
ram_size = Integer(dump_only=True, data_key='ramSize')
|
||||
data_storage_size = Integer(dump_only=True, data_key='dataStorageSize')
|
||||
processor_model = Str(dump_only=True, data_key='processorModel')
|
||||
graphic_card_model = Str(dump_only=True, data_key='graphicCardModel')
|
||||
network_speeds = List(Integer(dump_only=True), dump_only=True, data_key='networkSpeeds')
|
||||
|
||||
|
||||
class Desktop(Computer):
|
||||
|
@ -148,6 +162,7 @@ class DataStorage(Component):
|
|||
unit=UnitCodes.mbyte,
|
||||
description=m.DataStorage.size.comment)
|
||||
interface = EnumField(DataStorageInterface)
|
||||
privacy = EnumField(DataStoragePrivacyCompliance, dump_only=True)
|
||||
|
||||
|
||||
class HardDrive(DataStorage):
|
||||
|
|
|
@ -108,6 +108,7 @@ class DeviceSearch(db.Model):
|
|||
tags = session.query(
|
||||
search.Search.vectorize(
|
||||
(db.func.string_agg(Tag.id, ' '), search.Weight.A),
|
||||
(db.func.string_agg(Tag.secondary, ' '), search.Weight.A),
|
||||
(db.func.string_agg(Organization.name, ' '), search.Weight.B)
|
||||
)
|
||||
).filter(Tag.device_id == device.id).join(Tag.org)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
from enum import Enum
|
||||
|
||||
from ereuse_devicehub.resources.event import models as e
|
||||
|
||||
|
||||
class State(Enum):
|
||||
@classmethod
|
||||
def events(cls):
|
||||
"""Events participating in this state."""
|
||||
return (s.value for s in cls)
|
||||
|
||||
|
||||
class Trading(State):
|
||||
Reserved = e.Reserve
|
||||
Cancelled = e.CancelTrade
|
||||
Sold = e.Sell
|
||||
Donated = e.Donate
|
||||
Renting = e.Rent
|
||||
# todo add Pay = e.Pay
|
||||
ToBeDisposed = e.ToDisposeProduct
|
||||
ProductDisposed = e.DisposeProduct
|
||||
|
||||
|
||||
class Physical(State):
|
||||
ToBeRepaired = e.ToRepair
|
||||
Repaired = e.Repair
|
||||
Preparing = e.ToPrepare
|
||||
Prepared = e.Prepare
|
||||
ReadyToBeUsed = e.ReadyToUse
|
||||
InUse = e.Live
|
|
@ -235,3 +235,21 @@ class ReceiverRole(Enum):
|
|||
CollectionPoint = 'A collection point.'
|
||||
RecyclingPoint = 'A recycling point.'
|
||||
Transporter = 'An user that ships the devices to another one.'
|
||||
|
||||
|
||||
class DataStoragePrivacyCompliance(Enum):
|
||||
EraseBasic = 'EraseBasic'
|
||||
EraseBasicError = 'EraseBasicError'
|
||||
EraseSectors = 'EraseSectors'
|
||||
EraseSectorsError = 'EraseSectorsError'
|
||||
Destruction = 'Destruction'
|
||||
DestructionError = 'DestructionError'
|
||||
|
||||
@classmethod
|
||||
def from_erase(cls, erasure) -> 'DataStoragePrivacyCompliance':
|
||||
"""Returns the correct enum depending of the passed-in erasure."""
|
||||
from ereuse_devicehub.resources.event.models import EraseSectors
|
||||
if isinstance(erasure, EraseSectors):
|
||||
return cls.EraseSectors if not erasure.error else cls.EraseSectorsError
|
||||
else:
|
||||
return cls.EraseBasic if not erasure.error else cls.EraseBasicError
|
||||
|
|
|
@ -64,19 +64,9 @@ class WorkbenchRateDef(RateDef):
|
|||
SCHEMA = schemas.WorkbenchRate
|
||||
|
||||
|
||||
class PhotoboxUserDef(RateDef):
|
||||
class ManualRateDef(RateDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.PhotoboxUserRate
|
||||
|
||||
|
||||
class PhotoboxSystemRateDef(RateDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.PhotoboxSystemRate
|
||||
|
||||
|
||||
class AppRateDef(RateDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.AppRate
|
||||
SCHEMA = schemas.ManualRate
|
||||
|
||||
|
||||
class PriceDef(EventDef):
|
||||
|
@ -98,7 +88,8 @@ class SnapshotDef(EventDef):
|
|||
VIEW = SnapshotView
|
||||
SCHEMA = schemas.Snapshot
|
||||
|
||||
def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, static_url_path=None,
|
||||
def __init__(self, app, import_name=__name__.split('.')[0], 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,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from collections import Iterable
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_UP
|
||||
from distutils.version import StrictVersion
|
||||
from typing import Set, Union
|
||||
from uuid import uuid4
|
||||
|
||||
|
@ -24,17 +26,12 @@ from ereuse_devicehub.db import db
|
|||
from ereuse_devicehub.resources.agent.models import Agent
|
||||
from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Desktop, \
|
||||
Device, Laptop, Server
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, BOX_RATE_3, BOX_RATE_5, Bios, \
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, \
|
||||
FunctionalityRange, PriceSoftware, RATE_NEGATIVE, RATE_POSITIVE, RatingRange, RatingSoftware, \
|
||||
ReceiverRole, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.image.models import Image
|
||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
"""
|
||||
A quantity of money with a currency.
|
||||
"""
|
||||
|
||||
|
||||
class JoinedTableMixin:
|
||||
# noinspection PyMethodParameters
|
||||
|
@ -54,7 +51,7 @@ class Event(Thing):
|
|||
incidence.comment = """
|
||||
Should this event be reviewed due some anomaly?
|
||||
"""
|
||||
closed = Column(Boolean, default=False, nullable=False)
|
||||
closed = Column(Boolean, default=True, nullable=False)
|
||||
closed.comment = """
|
||||
Whether the author has finished the event.
|
||||
After this is set to True, no modifications are allowed.
|
||||
|
@ -360,8 +357,11 @@ class SnapshotRequest(db.Model):
|
|||
|
||||
class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
rating = Column(Float(decimal_return_scale=2), check_range('rating', *RATE_POSITIVE))
|
||||
rating.comment = """The rating for the content."""
|
||||
software = Column(DBEnum(RatingSoftware))
|
||||
software.comment = """The algorithm used to produce this rating."""
|
||||
version = Column(StrictVersionType)
|
||||
version.comment = """The version of the software."""
|
||||
appearance = Column(Float(decimal_return_scale=2), check_range('appearance', *RATE_NEGATIVE))
|
||||
functionality = Column(Float(decimal_return_scale=2),
|
||||
check_range('functionality', *RATE_NEGATIVE))
|
||||
|
@ -389,35 +389,16 @@ class IndividualRate(Rate):
|
|||
pass
|
||||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
ratings = relationship(IndividualRate,
|
||||
backref=backref('aggregated_ratings',
|
||||
lazy=True,
|
||||
order_by=lambda: IndividualRate.created,
|
||||
collection_class=OrderedSet),
|
||||
secondary=lambda: RateAggregateRate.__table__,
|
||||
order_by=lambda: IndividualRate.created,
|
||||
collection_class=OrderedSet)
|
||||
"""The ratings this aggregateRate aggregates."""
|
||||
|
||||
|
||||
class RateAggregateRate(db.Model):
|
||||
"""
|
||||
Represents the ``many to many`` relationship between
|
||||
``Rate`` and ``AggregateRate``.
|
||||
"""
|
||||
rate_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
aggregate_rate_id = Column(UUID(as_uuid=True),
|
||||
ForeignKey(AggregateRate.id),
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class ManualRate(IndividualRate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
labelling = Column(Boolean)
|
||||
labelling.comment = """Sets if there are labels stuck that should
|
||||
be removed.
|
||||
"""
|
||||
appearance_range = Column(DBEnum(AppearanceRange))
|
||||
appearance_range.comment = AppearanceRange.__doc__
|
||||
functionality_range = Column(DBEnum(FunctionalityRange))
|
||||
functionality_range.comment = FunctionalityRange.__doc__
|
||||
|
||||
|
||||
class WorkbenchRate(ManualRate):
|
||||
|
@ -428,63 +409,120 @@ class WorkbenchRate(ManualRate):
|
|||
check_range('data_storage', *RATE_POSITIVE))
|
||||
graphic_card = Column(Float(decimal_return_scale=2),
|
||||
check_range('graphic_card', *RATE_POSITIVE))
|
||||
bios = Column(DBEnum(Bios))
|
||||
bios = Column(Float(decimal_return_scale=2),
|
||||
check_range('bios', *RATE_POSITIVE))
|
||||
bios_range = Column(DBEnum(Bios))
|
||||
bios_range.comment = Bios.__doc__
|
||||
|
||||
# todo ensure for WorkbenchRate version and software are not None when inserting them
|
||||
|
||||
def ratings(self) -> Set['WorkbenchRate']:
|
||||
def ratings(self):
|
||||
"""
|
||||
Computes all the possible rates taking this rating as a model.
|
||||
|
||||
Returns a set of ratings, including this one, which is mutated.
|
||||
"""
|
||||
from ereuse_rate.main import main
|
||||
from ereuse_devicehub.resources.event.rate.main import main
|
||||
return main(self, **app.config.get_namespace('WORKBENCH_RATE_'))
|
||||
|
||||
|
||||
class AppRate(ManualRate):
|
||||
pass
|
||||
|
||||
|
||||
class PhotoboxRate(IndividualRate):
|
||||
class AggregateRate(Rate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
image_id = Column(UUID(as_uuid=True), ForeignKey(Image.id), nullable=False)
|
||||
image = relationship(Image,
|
||||
uselist=False,
|
||||
cascade=CASCADE_OWN,
|
||||
single_parent=True,
|
||||
primaryjoin=Image.id == image_id)
|
||||
manual_id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id))
|
||||
manual_id.comment = """The ManualEvent used to generate this
|
||||
aggregation, or None if none used.
|
||||
|
||||
# todo how to ensure phtoboxrate.device == image.image_list.device?
|
||||
An example of ManualEvent is using the web or the Android app
|
||||
to rate a device.
|
||||
"""
|
||||
manual = relationship(ManualRate,
|
||||
backref=backref('aggregate_rate_manual',
|
||||
lazy=True,
|
||||
order_by=lambda: AggregateRate.created,
|
||||
collection_class=OrderedSet),
|
||||
primaryjoin=manual_id == ManualRate.id)
|
||||
workbench_id = Column(UUID(as_uuid=True), ForeignKey(WorkbenchRate.id))
|
||||
workbench_id.comment = """The WorkbenchRate used to generate
|
||||
this aggregation, or None if none used.
|
||||
"""
|
||||
workbench = relationship(WorkbenchRate,
|
||||
backref=backref('aggregate_rate_workbench',
|
||||
lazy=True,
|
||||
order_by=lambda: AggregateRate.created,
|
||||
collection_class=OrderedSet),
|
||||
primaryjoin=workbench_id == WorkbenchRate.id)
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs.setdefault('version', StrictVersion('1.0'))
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
class PhotoboxUserRate(PhotoboxRate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(PhotoboxRate.id), primary_key=True)
|
||||
assembling = Column(SmallInteger, check_range('assembling', *BOX_RATE_5), nullable=False)
|
||||
parts = Column(SmallInteger, check_range('parts', *BOX_RATE_5), nullable=False)
|
||||
buttons = Column(SmallInteger, check_range('buttons', *BOX_RATE_5), nullable=False)
|
||||
dents = Column(SmallInteger, check_range('dents', *BOX_RATE_5), nullable=False)
|
||||
decolorization = Column(SmallInteger,
|
||||
check_range('decolorization', *BOX_RATE_5),
|
||||
nullable=False)
|
||||
scratches = Column(SmallInteger, check_range('scratches', *BOX_RATE_5), nullable=False)
|
||||
tag_alignment = Column(SmallInteger,
|
||||
check_range('tag_alignment', *BOX_RATE_3),
|
||||
nullable=False)
|
||||
tag_adhesive = Column(SmallInteger, check_range('tag_adhesive', *BOX_RATE_3), nullable=False)
|
||||
dirt = Column(SmallInteger, check_range('dirt', *BOX_RATE_3), nullable=False)
|
||||
# todo take value from LAST event (manual or workbench)
|
||||
|
||||
@property
|
||||
def processor(self):
|
||||
return self.workbench.processor
|
||||
|
||||
class PhotoboxSystemRate(PhotoboxRate):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(PhotoboxRate.id), primary_key=True)
|
||||
@property
|
||||
def ram(self):
|
||||
return self.workbench.ram
|
||||
|
||||
@property
|
||||
def data_storage(self):
|
||||
return self.workbench.data_storage
|
||||
|
||||
@property
|
||||
def graphic_card(self):
|
||||
return self.workbench.graphic_card
|
||||
|
||||
@property
|
||||
def bios(self):
|
||||
return self.workbench.bios
|
||||
|
||||
@property
|
||||
def functionality_range(self):
|
||||
return self.workbench.functionality_range
|
||||
|
||||
@property
|
||||
def appearance_range(self):
|
||||
return self.workbench.appearance_range
|
||||
|
||||
@property
|
||||
def bios_range(self):
|
||||
return self.workbench.bios_range
|
||||
|
||||
@property
|
||||
def labelling(self):
|
||||
return self.workbench.labelling
|
||||
|
||||
@classmethod
|
||||
def from_workbench_rate(cls, rate: WorkbenchRate):
|
||||
aggregate = cls()
|
||||
aggregate.rating = rate.rating
|
||||
aggregate.software = rate.software
|
||||
aggregate.appearance = rate.appearance
|
||||
aggregate.functionality = rate.functionality
|
||||
aggregate.device = rate.device
|
||||
aggregate.workbench = rate
|
||||
return aggregate
|
||||
|
||||
|
||||
class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
SCALE = 4
|
||||
ROUND = ROUND_HALF_EVEN
|
||||
currency = Column(DBEnum(Currency), nullable=False)
|
||||
price = Column(Numeric(precision=19, scale=4), check_range('price', 0), nullable=False)
|
||||
currency.comment = """The currency of this price as for ISO 4217."""
|
||||
price = Column(Numeric(precision=19, scale=SCALE), check_range('price', 0), nullable=False)
|
||||
price.comment = """The value."""
|
||||
software = Column(DBEnum(PriceSoftware))
|
||||
software.comment = """The software used to compute this price,
|
||||
if the price was computed automatically. This field is None
|
||||
if the price has been manually set.
|
||||
"""
|
||||
version = Column(StrictVersionType)
|
||||
version.comment = """The version of the software, or None."""
|
||||
rating_id = Column(UUID(as_uuid=True), ForeignKey(AggregateRate.id))
|
||||
rating_id.comment = """The AggregateRate used to auto-compute
|
||||
this price, if it has not been set manually."""
|
||||
rating = relationship(AggregateRate,
|
||||
backref=backref('price',
|
||||
lazy=True,
|
||||
|
@ -493,8 +531,17 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
primaryjoin=AggregateRate.id == rating_id)
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.currency = self.currency or app.config['PRICE_CURRENCY']
|
||||
if 'price' in kwargs:
|
||||
assert isinstance(kwargs['price'], Decimal), 'Price must be a Decimal'
|
||||
super().__init__(currency=kwargs.pop('currency', app.config['PRICE_CURRENCY']), **kwargs)
|
||||
|
||||
@classmethod
|
||||
def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal:
|
||||
"""Returns a Decimal value with the correct scale for Price.price."""
|
||||
if isinstance(value, float):
|
||||
value = Decimal(value)
|
||||
# equation from marshmallow.fields.Decimal
|
||||
return value.quantize(Decimal((0, (1,), -cls.SCALE)), rounding=rounding)
|
||||
|
||||
|
||||
class EreusePrice(Price):
|
||||
|
@ -505,9 +552,10 @@ class EreusePrice(Price):
|
|||
}
|
||||
|
||||
class Type:
|
||||
def __init__(self, percentage, price) -> None:
|
||||
def __init__(self, percentage: float, price: Decimal) -> None:
|
||||
# see https://stackoverflow.com/a/29651462 for the - 0.005
|
||||
self.amount = round(price * percentage - 0.005, 2)
|
||||
self.amount = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = EreusePrice.to_price(price * Decimal(percentage))
|
||||
self.percentage = round(percentage - 0.005, 2)
|
||||
|
||||
class Service:
|
||||
|
@ -543,20 +591,25 @@ class EreusePrice(Price):
|
|||
}
|
||||
SCHEMA[Server] = SCHEMA[Desktop]
|
||||
|
||||
def __init__(self, device, rating_range, role, price) -> None:
|
||||
def __init__(self, device, rating_range, role, price: Decimal) -> None:
|
||||
cls = device.__class__ if device.__class__ != Server else Desktop
|
||||
rate = self.SCHEMA[cls][rating_range]
|
||||
self.standard = EreusePrice.Type(rate['STD'][role], price)
|
||||
self.warranty2 = EreusePrice.Type(rate['WR2'][role], price)
|
||||
self.standard = EreusePrice.Type(rate[self.STANDARD][role], price)
|
||||
if self.WARRANTY2 in rate:
|
||||
self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price)
|
||||
|
||||
def __init__(self, rating: AggregateRate, **kwargs) -> None:
|
||||
if rating.rating_range == RatingRange.VERY_LOW:
|
||||
raise ValueError('Cannot compute price for Range.VERY_LOW')
|
||||
self.price = round(rating.rating * self.MULTIPLIER[rating.device.__class__], 2)
|
||||
super().__init__(rating=rating, device=rating.device, **kwargs)
|
||||
# We pass ROUND_UP strategy so price is always greater than what refurbisher... amounts
|
||||
price = self.to_price(rating.rating * self.MULTIPLIER[rating.device.__class__], ROUND_UP)
|
||||
super().__init__(rating=rating,
|
||||
device=rating.device,
|
||||
price=price,
|
||||
software=kwargs.pop('software', app.config['PRICE_SOFTWARE']),
|
||||
version=kwargs.pop('version', app.config['PRICE_VERSION']),
|
||||
**kwargs)
|
||||
self._compute()
|
||||
self.software = self.software or app.config['PRICE_SOFTWARE']
|
||||
self.version = self.version or app.config['PRICE_VERSION']
|
||||
|
||||
@orm.reconstructor
|
||||
def _compute(self):
|
||||
|
@ -567,6 +620,7 @@ class EreusePrice(Price):
|
|||
self.refurbisher = self._service(self.Service.REFURBISHER)
|
||||
self.retailer = self._service(self.Service.RETAILER)
|
||||
self.platform = self._service(self.Service.PLATFORM)
|
||||
if hasattr(self.refurbisher, 'warranty2'):
|
||||
self.warranty2 = round(self.refurbisher.warranty2.amount
|
||||
+ self.retailer.warranty2.amount
|
||||
+ self.platform.warranty2.amount, 2)
|
||||
|
|
|
@ -19,7 +19,6 @@ from ereuse_devicehub.resources.device.models import Component, Computer, Device
|
|||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||
PriceSoftware, RatingSoftware, ReceiverRole, SnapshotExpectedEvents, SnapshotSoftware, \
|
||||
TestDataStorageLength
|
||||
from ereuse_devicehub.resources.image.models import Image
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
|
@ -147,6 +146,8 @@ class Rate(EventWithOneDevice):
|
|||
rating = ... # type: Column
|
||||
appearance = ... # type: Column
|
||||
functionality = ... # type: Column
|
||||
software = ... # type: Column
|
||||
version = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
@ -165,59 +166,100 @@ class IndividualRate(Rate):
|
|||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
manual_id = ... # type: Column
|
||||
manual = ... # type: relationship
|
||||
workbench = ... # type: relationship
|
||||
workbench_id = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.ratings = ... # type: Set[IndividualRate]
|
||||
self.manual_id = ... # type: UUID
|
||||
self.manual = ... # type: ManualRate
|
||||
self.workbench = ... # type: WorkbenchRate
|
||||
self.workbench_id = ... # type: UUID
|
||||
self.price = ... # type: Price
|
||||
|
||||
@property
|
||||
def processor(self):
|
||||
return self.workbench.processor
|
||||
|
||||
@property
|
||||
def ram(self):
|
||||
return self.workbench.ram
|
||||
|
||||
@property
|
||||
def data_storage(self):
|
||||
return self.workbench.data_storage
|
||||
|
||||
@property
|
||||
def graphic_card(self):
|
||||
return self.workbench.graphic_card
|
||||
|
||||
@property
|
||||
def bios(self):
|
||||
return self.workbench.bios
|
||||
|
||||
@property
|
||||
def functionality_range(self):
|
||||
return self.workbench.functionality_range
|
||||
|
||||
@property
|
||||
def appearance_range(self):
|
||||
return self.workbench.appearance_range
|
||||
|
||||
@property
|
||||
def bios_range(self):
|
||||
return self.workbench.bios_range
|
||||
|
||||
@property
|
||||
def labelling(self):
|
||||
return self.workbench.labelling
|
||||
|
||||
@classmethod
|
||||
def from_workbench_rate(cls, rate: WorkbenchRate) -> AggregateRate:
|
||||
pass
|
||||
|
||||
|
||||
class ManualRate(IndividualRate):
|
||||
labelling = ... # type: Column
|
||||
appearance_range = ... # type: Column
|
||||
functionality_range = ... # type: Column
|
||||
aggregate_rate_manual = ... #type: relationship
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.labelling = ... # type: bool
|
||||
self.appearance_range = ... # type: AppearanceRange
|
||||
self.functionality_range = ... # type: FunctionalityRange
|
||||
self.aggregate_rate_manual = ... #type: AggregateRate
|
||||
|
||||
|
||||
class WorkbenchRate(ManualRate):
|
||||
processor = ... # type: Column
|
||||
ram = ... # type: Column
|
||||
data_storage = ... # type: Column
|
||||
graphic_card = ... # type: Column
|
||||
bios_range = ... # type: Column
|
||||
bios = ... # type: Column
|
||||
aggregate_rate_workbench = ... #type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.processor = ... # type: float
|
||||
self.ram = ... # type: float
|
||||
self.data_storage = ... # type: float
|
||||
self.graphic_card = ... # type: float
|
||||
self.bios = ... # type: Bios
|
||||
self.bios_range = ... # type: Bios
|
||||
self.bios = ... # type: float
|
||||
self.aggregate_rate_workbench = ... #type: AggregateRate
|
||||
|
||||
|
||||
class AppRate(ManualRate):
|
||||
pass
|
||||
|
||||
|
||||
class PhotoboxRate(IndividualRate):
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.num = ... # type: int
|
||||
self.image = ... # type: Image
|
||||
|
||||
|
||||
class PhotoboxUserRate(PhotoboxRate):
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.assembling = ... # type: int
|
||||
self.parts = ... # type: int
|
||||
self.buttons = ... # type: int
|
||||
self.dents = ... # type: int
|
||||
self.decolorization = ... # type: int
|
||||
self.scratches = ... # type: int
|
||||
self.tag_adhesive = ... # type: int
|
||||
self.dirt = ... # type: int
|
||||
|
||||
|
||||
class PhotoboxSystemRate(PhotoboxRate):
|
||||
def ratings(self) -> Set[Rate]:
|
||||
pass
|
||||
|
||||
|
||||
class Price(EventWithOneDevice):
|
||||
SCALE = ...
|
||||
ROUND = ...
|
||||
currency = ... # type: Column
|
||||
price = ... # type: Column
|
||||
software = ... # type: Column
|
||||
|
@ -233,12 +275,32 @@ class Price(EventWithOneDevice):
|
|||
self.version = ... # type: StrictVersion
|
||||
self.rating = ... # type: AggregateRate
|
||||
|
||||
@classmethod
|
||||
def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal:
|
||||
pass
|
||||
|
||||
|
||||
class EreusePrice(Price):
|
||||
MULTIPLIER = ... # type: Dict
|
||||
|
||||
class Type:
|
||||
def __init__(self, percentage, price) -> None:
|
||||
super().__init__()
|
||||
self.amount = ... # type: float
|
||||
self.percentage = ... # type: float
|
||||
|
||||
class Service:
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.standard = ... # type: EreusePrice.Type
|
||||
self.warranty2 = ... # type: EreusePrice.Type
|
||||
|
||||
def __init__(self, rating: AggregateRate, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.retailer = ... # type: EreusePrice.Service
|
||||
self.platform = ... # type: EreusePrice.Service
|
||||
self.refurbisher = ... # type: EreusePrice.Service
|
||||
self.warranty2 = ... # type: float
|
||||
|
||||
|
||||
class Test(EventWithOneDevice):
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
from contextlib import suppress
|
||||
from distutils.version import StrictVersion
|
||||
from typing import Set, Union
|
||||
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.enums import RatingSoftware
|
||||
from ereuse_devicehub.resources.event.models import AggregateRate, EreusePrice, Rate, \
|
||||
WorkbenchRate
|
||||
from ereuse_devicehub.resources.event.rate.workbench import v1_0
|
||||
|
||||
RATE_TYPES = {
|
||||
WorkbenchRate: {
|
||||
RatingSoftware.ECost: {
|
||||
'1.0': v1_0.Rate()
|
||||
},
|
||||
RatingSoftware.EMarket: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def rate(device: Device, rate: Rate):
|
||||
"""
|
||||
Rates the passed-in ``rate`` using values from the rate itself
|
||||
and the ``device``.
|
||||
|
||||
This method mutates ``rate``.
|
||||
|
||||
:param device: The device to use as a model.
|
||||
:param rate: A half-filled rate.
|
||||
"""
|
||||
cls = rate.__class__
|
||||
assert cls in RATE_TYPES, 'Rate type {} not supported.'.format(cls)
|
||||
assert rate.software in RATE_TYPES[cls], 'Rate soft {} not supported.'.format(rate.software)
|
||||
assert str(rate.version) in RATE_TYPES[cls][rate.software], \
|
||||
'Rate version {} not supported.'.format(rate.version)
|
||||
RATE_TYPES[cls][rate.software][str(rate.version)].compute(device, rate)
|
||||
|
||||
|
||||
def main(rating_model: WorkbenchRate,
|
||||
software: RatingSoftware,
|
||||
version: StrictVersion) -> Set[Union[WorkbenchRate, AggregateRate, EreusePrice]]:
|
||||
"""
|
||||
Generates all the rates (per software and version) for a given
|
||||
half-filled rate acting as a model, and finally it generates
|
||||
an ``AggregateRating`` with the rate that matches the
|
||||
``software`` and ``version``.
|
||||
|
||||
This method mutates ``rating_model`` by fulfilling it and
|
||||
``rating_model.device`` by adding the new rates.
|
||||
|
||||
:return: A set of rates with the ``rate`` value computed, where
|
||||
the first rate is the ``rating_model``.
|
||||
"""
|
||||
assert rating_model.device
|
||||
events = set()
|
||||
for soft, value in RATE_TYPES[rating_model.__class__].items():
|
||||
for vers, func in value.items():
|
||||
if not rating_model.rating: # Fill the rating before creating another rate
|
||||
rating = rating_model
|
||||
else: # original rating was filled already; use a new one
|
||||
rating = WorkbenchRate(
|
||||
labelling=rating_model.labelling,
|
||||
appearance_range=rating_model.appearance_range,
|
||||
functionality_range=rating_model.functionality_range,
|
||||
device=rating_model.device,
|
||||
)
|
||||
rating.software = soft
|
||||
rating.version = vers
|
||||
rate(rating_model.device, rating)
|
||||
events.add(rating)
|
||||
if soft == software and vers == version:
|
||||
aggregation = AggregateRate.from_workbench_rate(rating)
|
||||
events.add(aggregation)
|
||||
with suppress(ValueError):
|
||||
# We will have exception if range == VERY_LOW
|
||||
events.add(EreusePrice(aggregation))
|
||||
return events
|
|
@ -0,0 +1,54 @@
|
|||
import math
|
||||
from typing import Iterable
|
||||
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.event.models import WorkbenchRate
|
||||
|
||||
|
||||
class BaseRate:
|
||||
"""growing exponential from this value"""
|
||||
CEXP = 0
|
||||
"""growing lineal starting on this value"""
|
||||
CLIN = 242
|
||||
"""growing logarithmic starting on this value"""
|
||||
CLOG = 0.5
|
||||
|
||||
"""Processor has 50% of weight over total score, used in harmonic mean"""
|
||||
PROCESSOR_WEIGHT = 0.5
|
||||
"""Storage has 20% of weight over total score, used in harmonic mean"""
|
||||
DATA_STORAGE_WEIGHT = 0.2
|
||||
"""Ram has 30% of weight over total score, used in harmonic mean"""
|
||||
RAM_WEIGHT = 0.3
|
||||
|
||||
def compute(self, device: Device, rate: WorkbenchRate):
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def norm(x, x_min, x_max):
|
||||
return (x - x_min) / (x_max - x_min)
|
||||
|
||||
@staticmethod
|
||||
def rate_log(x):
|
||||
return math.log10(2 * x) + 3.57 # todo magic number!
|
||||
|
||||
@staticmethod
|
||||
def rate_lin(x):
|
||||
return 7 * x + 0.06 # todo magic number!
|
||||
|
||||
@staticmethod
|
||||
def rate_exp(x):
|
||||
return math.exp(x) / (2 - math.exp(x))
|
||||
|
||||
@staticmethod
|
||||
def harmonic_mean(weights: Iterable[float], rates: Iterable[float]):
|
||||
return sum(weights) / sum(char / rate for char, rate in zip(weights, rates))
|
||||
|
||||
def harmonic_mean_rates(self, rate_processor, rate_storage, rate_ram):
|
||||
"""
|
||||
Merging components
|
||||
"""
|
||||
total_weights = self.PROCESSOR_WEIGHT + self.DATA_STORAGE_WEIGHT + self.RAM_WEIGHT
|
||||
total_rate = self.PROCESSOR_WEIGHT / rate_processor \
|
||||
+ self.DATA_STORAGE_WEIGHT / rate_storage \
|
||||
+ self.RAM_WEIGHT / rate_ram
|
||||
return total_weights / total_rate
|
|
@ -0,0 +1,253 @@
|
|||
from enum import Enum
|
||||
from itertools import groupby
|
||||
from typing import Iterable
|
||||
|
||||
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Desktop, Laptop, \
|
||||
Processor, RamModule, Server
|
||||
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
|
||||
WorkbenchRate
|
||||
# todo if no return assign then rate_c = 1 is assigned
|
||||
# todo fix corner cases, like components characteristics == None
|
||||
from ereuse_devicehub.resources.event.rate.rate import BaseRate
|
||||
|
||||
|
||||
class Rate(BaseRate):
|
||||
"""
|
||||
Rate all components in Computer
|
||||
"""
|
||||
|
||||
class Range(Enum):
|
||||
@classmethod
|
||||
def from_devicehub(cls, r: Enum):
|
||||
return getattr(cls, r.name) if r else cls.NONE
|
||||
|
||||
class Appearance(Range):
|
||||
Z = 0.5
|
||||
A = 0.3
|
||||
B = 0
|
||||
C = -0.2
|
||||
D = -0.5
|
||||
E = -1.0
|
||||
NONE = -0.3
|
||||
|
||||
class Functionality(Range):
|
||||
A = 0.4
|
||||
B = -0.5
|
||||
C = -0.75
|
||||
D = -1
|
||||
NONE = -0.3
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.RATES = {
|
||||
# composition: type: (field, compute class)
|
||||
Processor.t: ('processor', ProcessorRate()),
|
||||
RamModule.t: ('ram', RamRate()),
|
||||
DataStorage.t: ('data_storage', DataStorageRate())
|
||||
}
|
||||
|
||||
def compute(self, device: Computer, rate: WorkbenchRate):
|
||||
"""
|
||||
Compute 'Workbench'Rate computer is a rate (score) ranging from 0 to 4.7
|
||||
that represents estimating value of use of desktop and laptop computer components.
|
||||
|
||||
This mutates "rate".
|
||||
"""
|
||||
assert isinstance(device, (Desktop, Laptop, Server))
|
||||
assert isinstance(rate, WorkbenchRate)
|
||||
|
||||
rate.processor = rate.data_storage = rate.ram = 1 # Init
|
||||
|
||||
# Group cpus, rams, storages and compute their rate
|
||||
# Treat the same way with HardDrive and SolidStateDrive like (DataStorage)
|
||||
clause = lambda x: DataStorage.t if isinstance(x, DataStorage) else x.t
|
||||
c = (c for c in device.components if clause(c) in set(self.RATES.keys()))
|
||||
for type, components in groupby(sorted(c, key=clause), key=clause):
|
||||
if type == Processor.t: # ProcessorRate.compute expects only 1 processor
|
||||
components = next(components)
|
||||
field, rate_cls = self.RATES[type] # type: str, BaseRate
|
||||
result = rate_cls.compute(components, rate)
|
||||
if result:
|
||||
setattr(rate, field, result)
|
||||
|
||||
rate_components = self.harmonic_mean_rates(rate.processor, rate.data_storage, rate.ram)
|
||||
rate.appearance = self.Appearance.from_devicehub(rate.appearance_range).value
|
||||
rate.functionality = self.Functionality.from_devicehub(rate.functionality_range).value
|
||||
|
||||
rate.rating = round(max(rate_components + rate.functionality + rate.appearance, 0), 2)
|
||||
rate.appearance = round(rate.appearance, 2)
|
||||
rate.functionality = round(rate.functionality, 2)
|
||||
rate.processor = round(rate.processor, 2)
|
||||
rate.ram = round(rate.ram, 2)
|
||||
rate.data_storage = round(rate.data_storage, 2)
|
||||
|
||||
|
||||
class ProcessorRate(BaseRate):
|
||||
"""
|
||||
Calculate a ProcessorRate of all Processor devices
|
||||
"""
|
||||
# processor.xMin, processor.xMax
|
||||
PROCESSOR_NORM = 3196.17, 17503.81
|
||||
|
||||
DEFAULT_CORES = 1
|
||||
DEFAULT_SPEED = 1.6
|
||||
# In case of i2, i3,.. result penalized.
|
||||
# Intel(R) Core(TM) i3 CPU 530 @ 2.93GHz, score = 23406.92 but results inan score of 17503.
|
||||
DEFAULT_SCORE = 4000
|
||||
|
||||
def compute(self, processor: Processor, rate: WorkbenchRate):
|
||||
""" Compute processor rate
|
||||
Obs: cores and speed are possible NULL value
|
||||
:return: result is a rate (score) of Processor characteristics
|
||||
"""
|
||||
# todo for processor_device in processors; more than one processor
|
||||
cores = processor.cores or self.DEFAULT_CORES
|
||||
speed = processor.speed or self.DEFAULT_SPEED
|
||||
# todo fix StopIteration if don't exists BenchmarkProcessor
|
||||
benchmark_cpu = next(e for e in processor.events if isinstance(e, BenchmarkProcessor))
|
||||
benchmark_cpu = benchmark_cpu.rate or self.DEFAULT_SCORE
|
||||
|
||||
# STEP: Fusion components
|
||||
processor_rate = (benchmark_cpu + speed * 2000 * cores) / 2 # todo magic number!
|
||||
|
||||
# STEP: Normalize values
|
||||
processor_norm = max(self.norm(processor_rate, *self.PROCESSOR_NORM), 0)
|
||||
|
||||
# STEP: Compute rate/score from every component
|
||||
# Calculate processor_rate
|
||||
if processor_norm >= self.CEXP:
|
||||
processor_rate = self.rate_exp(processor_norm)
|
||||
if self.CLIN <= processor_norm < self.CLOG:
|
||||
processor_rate = self.rate_lin(processor_norm)
|
||||
if processor_norm >= self.CLOG:
|
||||
processor_rate = self.rate_log(processor_norm)
|
||||
|
||||
assert processor_rate, 'Could not rate processor.'
|
||||
return processor_rate
|
||||
|
||||
|
||||
class RamRate(BaseRate):
|
||||
"""
|
||||
Calculate a RamRate of all RamModule devices
|
||||
"""
|
||||
# ram.size.xMin; ram.size.xMax
|
||||
SIZE_NORM = 256, 8192
|
||||
RAM_SPEED_NORM = 133, 1333
|
||||
# ram.speed.factor
|
||||
RAM_SPEED_FACTOR = 3.7
|
||||
# ram.size.weight; ram.speed.weight;
|
||||
RAM_WEIGHTS = 0.7, 0.3
|
||||
|
||||
def compute(self, ram_devices: Iterable[RamModule], rate: WorkbenchRate):
|
||||
"""
|
||||
Obs: RamModule.speed is possible NULL value & size != NULL or NOT??
|
||||
:return: result is a rate (score) of all RamModule components
|
||||
"""
|
||||
size = 0.0
|
||||
speed = 0.0
|
||||
|
||||
# STEP: Filtering, data cleaning and merging of component parts
|
||||
for ram in ram_devices:
|
||||
_size = ram.size or 0
|
||||
size += _size
|
||||
if ram.speed:
|
||||
speed += (ram.speed or 0) * _size
|
||||
else:
|
||||
speed += (_size / self.RAM_SPEED_FACTOR) * _size
|
||||
|
||||
# STEP: Fusion components
|
||||
# To guarantee that there will be no 0/0
|
||||
if size:
|
||||
speed /= size
|
||||
|
||||
# STEP: Normalize values
|
||||
size_norm = max(self.norm(size, *self.SIZE_NORM), 0)
|
||||
ram_speed_norm = max(self.norm(speed, *self.RAM_SPEED_NORM), 0)
|
||||
|
||||
# STEP: Compute rate/score from every component
|
||||
# Calculate size_rate
|
||||
if self.CEXP <= size_norm < self.CLIN:
|
||||
size_rate = self.rate_exp(size_norm)
|
||||
if self.CLIN <= size_norm < self.CLOG:
|
||||
size_rate = self.rate_lin(size_norm)
|
||||
if size_norm >= self.CLOG:
|
||||
size_rate = self.rate_log(size_norm)
|
||||
# Calculate ram_speed_rate
|
||||
if self.CEXP <= ram_speed_norm < self.CLIN:
|
||||
ram_speed_rate = self.rate_exp(ram_speed_norm)
|
||||
if self.CLIN <= ram_speed_norm < self.CLOG:
|
||||
ram_speed_rate = self.rate_lin(ram_speed_norm)
|
||||
if ram_speed_norm >= self.CLOG:
|
||||
ram_speed_rate = self.rate_log(ram_speed_norm)
|
||||
|
||||
# STEP: Fusion Characteristics
|
||||
return self.harmonic_mean(self.RAM_WEIGHTS, rates=(size_rate, ram_speed_rate))
|
||||
|
||||
|
||||
class DataStorageRate(BaseRate):
|
||||
"""
|
||||
Calculate the rate of all DataStorage devices
|
||||
"""
|
||||
# drive.size.xMin; drive.size.xMax
|
||||
SIZE_NORM = 4, 265000
|
||||
READ_SPEED_NORM = 2.7, 109.5
|
||||
WRITE_SPEED_NORM = 2, 27.35
|
||||
# drive.size.weight; drive.readingSpeed.weight; drive.writingSpeed.weight;
|
||||
DATA_STORAGE_WEIGHTS = 0.5, 0.25, 0.25
|
||||
|
||||
def compute(self, data_storage_devices: Iterable[DataStorage], rate: WorkbenchRate):
|
||||
"""
|
||||
Obs: size != NULL and 0 value & read_speed and write_speed != NULL
|
||||
:return: result is a rate (score) of all DataStorage devices
|
||||
"""
|
||||
size = 0
|
||||
read_speed = 0
|
||||
write_speed = 0
|
||||
|
||||
# STEP: Filtering, data cleaning and merging of component parts
|
||||
for storage in data_storage_devices:
|
||||
# todo fix StopIteration if don't exists BenchmarkDataStorage
|
||||
benchmark = next(e for e in storage.events if isinstance(e, BenchmarkDataStorage))
|
||||
# prevent NULL values
|
||||
_size = storage.size or 0
|
||||
size += _size
|
||||
read_speed += benchmark.read_speed * _size
|
||||
write_speed += benchmark.write_speed * _size
|
||||
|
||||
# STEP: Fusion components
|
||||
# Check almost one storage have size, try catch exception 0/0
|
||||
if size:
|
||||
read_speed /= size
|
||||
write_speed /= size
|
||||
|
||||
# STEP: Normalize values
|
||||
size_norm = max(self.norm(size, *self.SIZE_NORM), 0)
|
||||
read_speed_norm = max(self.norm(read_speed, *self.READ_SPEED_NORM), 0)
|
||||
write_speed_norm = max(self.norm(write_speed, *self.WRITE_SPEED_NORM), 0)
|
||||
|
||||
# STEP: Compute rate/score from every component
|
||||
# Calculate size_rate
|
||||
if size_norm >= self.CLOG:
|
||||
size_rate = self.rate_log(size_norm)
|
||||
elif self.CLIN <= size_norm < self.CLOG:
|
||||
size_rate = self.rate_lin(size_norm)
|
||||
elif self.CEXP <= size_norm < self.CLIN:
|
||||
size_rate = self.rate_exp(size_norm)
|
||||
# Calculate read_speed_rate
|
||||
if read_speed_norm >= self.CLOG:
|
||||
read_speed_rate = self.rate_log(read_speed_norm)
|
||||
elif self.CLIN <= read_speed_norm < self.CLOG:
|
||||
read_speed_rate = self.rate_lin(read_speed_norm)
|
||||
elif self.CEXP <= read_speed_norm < self.CLIN:
|
||||
read_speed_rate = self.rate_exp(read_speed_norm)
|
||||
# write_speed_rate
|
||||
if write_speed_norm >= self.CLOG:
|
||||
write_speed_rate = self.rate_log(write_speed_norm)
|
||||
elif self.CLIN <= write_speed_norm < self.CLOG:
|
||||
write_speed_rate = self.rate_lin(write_speed_norm)
|
||||
elif self.CEXP <= write_speed_norm < self.CLIN:
|
||||
write_speed_rate = self.rate_exp(write_speed_norm)
|
||||
|
||||
# STEP: Fusion Characteristics
|
||||
return self.harmonic_mean(self.DATA_STORAGE_WEIGHTS,
|
||||
rates=(size_rate, read_speed_rate, write_speed_rate))
|
|
@ -1,5 +1,3 @@
|
|||
import decimal
|
||||
|
||||
from flask import current_app as app
|
||||
from marshmallow import Schema as MarshmallowSchema, ValidationError, validates_schema
|
||||
from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, List, Nested, String, \
|
||||
|
@ -101,62 +99,30 @@ class StepRandom(Step):
|
|||
class Rate(EventWithOneDevice):
|
||||
rating = Integer(validate=Range(*RATE_POSITIVE),
|
||||
dump_only=True,
|
||||
data_key='ratingValue',
|
||||
description='The rating for the content.')
|
||||
description=m.Rate.rating.comment)
|
||||
software = EnumField(RatingSoftware,
|
||||
dump_only=True,
|
||||
description='The algorithm used to produce this rating.')
|
||||
description=m.Rate.software.comment)
|
||||
version = Version(dump_only=True,
|
||||
description='The version of the software.')
|
||||
description=m.Rate.version.comment)
|
||||
appearance = Integer(validate=Range(-3, 5), dump_only=True)
|
||||
functionality = Integer(validate=Range(-3, 5),
|
||||
dump_only=True,
|
||||
data_key='functionalityScore')
|
||||
functionality = Integer(validate=Range(-3, 5), dump_only=True)
|
||||
|
||||
|
||||
class IndividualRate(Rate):
|
||||
pass
|
||||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
ratings = NestedOn(IndividualRate, many=True)
|
||||
|
||||
|
||||
class PhotoboxRate(IndividualRate):
|
||||
num = Integer(dump_only=True)
|
||||
# todo Image
|
||||
|
||||
|
||||
class PhotoboxUserRate(IndividualRate):
|
||||
assembling = Integer()
|
||||
parts = Integer()
|
||||
buttons = Integer()
|
||||
dents = Integer()
|
||||
decolorization = Integer()
|
||||
scratches = Integer()
|
||||
tag_adhesive = Integer()
|
||||
dirt = Integer()
|
||||
|
||||
|
||||
class PhotoboxSystemRate(IndividualRate):
|
||||
pass
|
||||
|
||||
|
||||
class ManualRate(IndividualRate):
|
||||
appearance_range = EnumField(AppearanceRange,
|
||||
required=True,
|
||||
data_key='appearanceRange',
|
||||
description='Grades the imperfections that aesthetically '
|
||||
'affect the device, but not its usage.')
|
||||
description=m.ManualRate.appearance_range.comment)
|
||||
functionality_range = EnumField(FunctionalityRange,
|
||||
required=True,
|
||||
data_key='functionalityRange',
|
||||
description='Grades the defects of a device affecting usage.')
|
||||
labelling = Boolean(description='Sets if there are labels stuck that should be removed.')
|
||||
|
||||
|
||||
class AppRate(ManualRate):
|
||||
pass
|
||||
description=m.ManualRate.functionality_range.comment)
|
||||
labelling = Boolean(description=m.ManualRate.labelling.comment)
|
||||
|
||||
|
||||
class WorkbenchRate(ManualRate):
|
||||
|
@ -164,16 +130,46 @@ class WorkbenchRate(ManualRate):
|
|||
ram = Float()
|
||||
data_storage = Float()
|
||||
graphic_card = Float()
|
||||
bios = EnumField(Bios, description='How difficult it has been to set the bios to '
|
||||
'boot from the network.')
|
||||
bios = Float()
|
||||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchRate.bios_range.comment,
|
||||
data_key='biosRange')
|
||||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
workbench = NestedOn(WorkbenchRate, dump_only=True,
|
||||
description=m.AggregateRate.workbench_id.comment)
|
||||
manual = NestedOn(ManualRate,
|
||||
dump_only=True,
|
||||
description=m.AggregateRate.manual_id.comment)
|
||||
processor = Float(dump_only=True)
|
||||
ram = Float(dump_only=True)
|
||||
data_storage = Float(dump_only=True)
|
||||
graphic_card = Float(dump_only=True)
|
||||
bios = EnumField(Bios, dump_only=True)
|
||||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchRate.bios_range.comment,
|
||||
data_key='biosRange')
|
||||
appearance_range = EnumField(AppearanceRange,
|
||||
required=True,
|
||||
data_key='appearanceRange',
|
||||
description=m.ManualRate.appearance_range.comment)
|
||||
functionality_range = EnumField(FunctionalityRange,
|
||||
required=True,
|
||||
data_key='functionalityRange',
|
||||
description=m.ManualRate.functionality_range.comment)
|
||||
labelling = Boolean(description=m.ManualRate.labelling.comment)
|
||||
|
||||
|
||||
class Price(EventWithOneDevice):
|
||||
currency = EnumField(Currency, required=True)
|
||||
price = Decimal(places=4, rounding=decimal.ROUND_HALF_EVEN, required=True)
|
||||
software = EnumField(PriceSoftware, dump_only=True)
|
||||
version = Version(dump_only=True)
|
||||
rating = NestedOn(AggregateRate, dump_only=True)
|
||||
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
|
||||
price = Decimal(places=m.Price.SCALE,
|
||||
rounding=m.Price.ROUND,
|
||||
required=True,
|
||||
description=m.Price.price.comment)
|
||||
software = EnumField(PriceSoftware, dump_only=True, description=m.Price.software.comment)
|
||||
version = Version(dump_only=True, description=m.Price.version.comment)
|
||||
rating = NestedOn(AggregateRate, dump_only=True, description=m.Price.rating_id.comment)
|
||||
|
||||
|
||||
class EreusePrice(Price):
|
||||
|
@ -285,7 +281,7 @@ class StressTest(Test):
|
|||
|
||||
|
||||
class Benchmark(EventWithOneDevice):
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from contextlib import suppress
|
||||
from distutils.version import StrictVersion
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
@ -77,11 +76,9 @@ class SnapshotView(View):
|
|||
snapshot.events |= events
|
||||
|
||||
# Compute ratings
|
||||
with suppress(StopIteration):
|
||||
# todo are we sure we want to have snapshots without rates?
|
||||
snapshot.events |= next(
|
||||
e.ratings() for e in events_device if isinstance(e, WorkbenchRate)
|
||||
)
|
||||
for rate in (e for e in events_device if isinstance(e, WorkbenchRate)):
|
||||
rates = rate.ratings()
|
||||
snapshot.events |= rates
|
||||
|
||||
db.session.add(snapshot)
|
||||
db.session.commit()
|
||||
|
|
|
@ -15,7 +15,8 @@ class LotDef(Resource):
|
|||
AUTH = True
|
||||
ID_CONVERTER = Converters.uuid
|
||||
|
||||
def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, static_url_path=None,
|
||||
def __init__(self, app, import_name=__name__.split('.')[0], 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,
|
||||
|
|
|
@ -75,8 +75,8 @@ class Tag(Thing):
|
|||
return url
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(device_id, org_id, name='one_tag_per_org'),
|
||||
UniqueConstraint(secondary, org_id, name='one_secondary_per_org')
|
||||
UniqueConstraint(id, org_id, name='one tag id per organization'),
|
||||
UniqueConstraint(secondary, org_id, name='one secondary tag per organization')
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
|
@ -5,7 +5,6 @@ click==6.7
|
|||
click-spinner==0.1.8
|
||||
colorama==0.3.9
|
||||
colour==0.1.5
|
||||
ereuse-rate==0.0.2
|
||||
ereuse-utils==0.4.0b9
|
||||
Flask==1.0.2
|
||||
Flask-Cors==3.0.6
|
||||
|
|
1
setup.py
1
setup.py
|
@ -37,7 +37,6 @@ setup(
|
|||
'teal>=0.2.0a24', # teal always first
|
||||
'click',
|
||||
'click-spinner',
|
||||
'ereuse-rate==0.0.2',
|
||||
'ereuse-utils[Naming]>=0.4b9',
|
||||
'hashids',
|
||||
'marshmallow_enum',
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
{
|
||||
"closed": true,
|
||||
"components": [
|
||||
{
|
||||
"events": [],
|
||||
"manufacturer": "Intel Corporation",
|
||||
"model": "NM10/ICH7 Family High Definition Audio Controller",
|
||||
"serialNumber": null,
|
||||
"type": "SoundCard"
|
||||
},
|
||||
{
|
||||
"events": [],
|
||||
"manufacturer": "Azurewave",
|
||||
"model": "USB 2.0 UVC VGA WebCam",
|
||||
"serialNumber": "0x0001",
|
||||
"type": "SoundCard"
|
||||
},
|
||||
{
|
||||
"events": [],
|
||||
"format": "DIMM",
|
||||
"interface": "DDR2",
|
||||
"manufacturer": null,
|
||||
"model": null,
|
||||
"serialNumber": null,
|
||||
"size": 1024,
|
||||
"speed": 667.0,
|
||||
"type": "RamModule"
|
||||
},
|
||||
{
|
||||
"address": 64,
|
||||
"cores": 1,
|
||||
"events": [
|
||||
{
|
||||
"elapsed": 165,
|
||||
"rate": 164.8342,
|
||||
"type": "BenchmarkProcessorSysbench"
|
||||
},
|
||||
{
|
||||
"elapsed": 0,
|
||||
"rate": 6665.7,
|
||||
"type": "BenchmarkProcessor"
|
||||
}
|
||||
],
|
||||
"manufacturer": "Intel Corp.",
|
||||
"model": "Intel Atom CPU N455 @ 1.66GHz",
|
||||
"serialNumber": null,
|
||||
"speed": 1.667,
|
||||
"threads": 2,
|
||||
"type": "Processor"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"elapsed": 16,
|
||||
"readSpeed": 66.2,
|
||||
"type": "BenchmarkDataStorage",
|
||||
"writeSpeed": 21.8
|
||||
}
|
||||
],
|
||||
"interface": "ATA",
|
||||
"manufacturer": "Hitachi",
|
||||
"model": "HTS54322",
|
||||
"serialNumber": "E2024242CV86HJ",
|
||||
"size": 238475,
|
||||
"type": "HardDrive"
|
||||
},
|
||||
{
|
||||
"events": [],
|
||||
"manufacturer": "Qualcomm Atheros",
|
||||
"model": "AR9285 Wireless Network Adapter",
|
||||
"serialNumber": "74:2f:68:8b:fd:c8",
|
||||
"type": "NetworkAdapter",
|
||||
"wireless": true
|
||||
},
|
||||
{
|
||||
"events": [],
|
||||
"manufacturer": "Qualcomm Atheros",
|
||||
"model": "AR8152 v2.0 Fast Ethernet",
|
||||
"serialNumber": "14:da:e9:42:f6:7c",
|
||||
"speed": 100,
|
||||
"type": "NetworkAdapter",
|
||||
"wireless": false
|
||||
},
|
||||
{
|
||||
"events": [],
|
||||
"manufacturer": "Intel Corporation",
|
||||
"memory": 256.0,
|
||||
"model": "Atom Processor D4xx/D5xx/N4xx/N5xx Integrated Graphics Controller",
|
||||
"serialNumber": null,
|
||||
"type": "GraphicCard"
|
||||
},
|
||||
{
|
||||
"events": [],
|
||||
"firewire": 0,
|
||||
"manufacturer": "ASUSTeK Computer INC.",
|
||||
"model": "1001PXD",
|
||||
"pcmcia": 0,
|
||||
"serial": 1,
|
||||
"serialNumber": "Eee0123456789",
|
||||
"slots": 2,
|
||||
"type": "Motherboard",
|
||||
"usb": 5
|
||||
}
|
||||
],
|
||||
"device": {
|
||||
"chassis": "Netbook",
|
||||
"events": [
|
||||
{
|
||||
"elapsed": 16,
|
||||
"rate": 15.8978,
|
||||
"type": "BenchmarkRamSysbench"
|
||||
},
|
||||
{
|
||||
"appearanceRange": "A",
|
||||
"biosRange": "A",
|
||||
"functionalityRange": "A",
|
||||
"type": "WorkbenchRate"
|
||||
}
|
||||
],
|
||||
"manufacturer": "ASUSTeK Computer INC.",
|
||||
"model": "1001PXD",
|
||||
"serialNumber": "B8OAAS048286",
|
||||
"type": "Laptop"
|
||||
},
|
||||
"elapsed": 6,
|
||||
"endTime": "2018-10-14T21:22:14.777235+00:00",
|
||||
"expectedEvents": [
|
||||
"Benchmark"
|
||||
],
|
||||
"software": "Workbench",
|
||||
"type": "Snapshot",
|
||||
"uuid": "7dc4d19c-914e-4652-a381-d641325fb9c2",
|
||||
"version": "11.0a6"
|
||||
}
|
|
@ -14,7 +14,7 @@ device:
|
|||
appearanceRange: A
|
||||
functionalityRange: B
|
||||
labelling: True
|
||||
bios: B
|
||||
biosRange: B
|
||||
components:
|
||||
- type: GraphicCard
|
||||
serialNumber: gc1s
|
||||
|
@ -33,3 +33,4 @@ components:
|
|||
events:
|
||||
- type: BenchmarkProcessor
|
||||
rate: 2410
|
||||
elapsed: 11
|
||||
|
|
|
@ -39,4 +39,4 @@ def test_api_docs(client: Client):
|
|||
'scheme': 'basic',
|
||||
'name': 'Authorization'
|
||||
}
|
||||
assert 77 == len(docs['definitions'])
|
||||
assert 75 == len(docs['definitions'])
|
||||
|
|
|
@ -91,7 +91,14 @@ def test_physical_properties():
|
|||
manufacturer='mr',
|
||||
width=2.0,
|
||||
color=Color())
|
||||
pc = Desktop(chassis=ComputerChassis.Tower)
|
||||
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'))
|
||||
pc.components.add(c)
|
||||
db.session.add(pc)
|
||||
db.session.commit()
|
||||
|
@ -110,6 +117,17 @@ def test_physical_properties():
|
|||
'color': Color(),
|
||||
'depth': None
|
||||
}
|
||||
assert pc.physical_properties == {
|
||||
'model': 'foo',
|
||||
'manufacturer': 'bar',
|
||||
'serial_number': 'foo-bar',
|
||||
'weight': 2.8,
|
||||
'width': 1.4,
|
||||
'height': 2.1,
|
||||
'depth': None,
|
||||
'color': Color('LightSeaGreen'),
|
||||
'chassis': ComputerChassis.Tower
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import ipaddress
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
from flask import current_app as app, g
|
||||
|
@ -8,6 +10,7 @@ from teal.enums import Currency, Subdivision
|
|||
|
||||
from ereuse_devicehub.client import UserClient
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device import states
|
||||
from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \
|
||||
RamModule, SolidStateDrive
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, TestDataStorageLength
|
||||
|
@ -175,22 +178,24 @@ def test_update_parent():
|
|||
assert not benchmark.parent
|
||||
|
||||
|
||||
@pytest.mark.parametrize('event_model', [
|
||||
models.ToRepair,
|
||||
models.Repair,
|
||||
models.ToPrepare,
|
||||
models.ReadyToUse,
|
||||
models.ToPrepare,
|
||||
models.Prepare,
|
||||
@pytest.mark.parametrize('event_model_state', [
|
||||
(models.ToRepair, states.Physical.ToBeRepaired),
|
||||
(models.Repair, states.Physical.Repaired),
|
||||
(models.ToPrepare, states.Physical.Preparing),
|
||||
(models.ReadyToUse, states.Physical.ReadyToBeUsed),
|
||||
(models.ToPrepare, states.Physical.Preparing),
|
||||
(models.Prepare, states.Physical.Prepared)
|
||||
])
|
||||
def test_generic_event(event_model: models.Event, user: UserClient):
|
||||
def test_generic_event(event_model_state: Tuple[models.Event, states.Trading], user: UserClient):
|
||||
"""Tests POSTing all generic events."""
|
||||
event_model, state = event_model_state
|
||||
snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
|
||||
event = {'type': event_model.t, 'devices': [snapshot['device']['id']]}
|
||||
event, _ = user.post(event, res=models.Event)
|
||||
assert event['devices'][0]['id'] == snapshot['device']['id']
|
||||
device, _ = user.get(res=Device, item=snapshot['device']['id'])
|
||||
assert device['events'][-1]['id'] == event['id']
|
||||
assert device['physical'] == state.name
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
|
@ -214,6 +219,8 @@ def test_live():
|
|||
assert live['ip'] == '79.147.10.10'
|
||||
assert live['subdivision'] == 'ES-CA'
|
||||
assert live['country'] == 'ES'
|
||||
device, _ = client.get(res=Device, item=live['device']['id'])
|
||||
assert device['physical'] == states.Physical.InUse.name
|
||||
|
||||
|
||||
@pytest.mark.xfail(reson='Functionality not developed.')
|
||||
|
@ -226,14 +233,15 @@ def test_reserve(user: UserClient):
|
|||
"""Performs a reservation and then cancels it."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize('event_model', [
|
||||
models.Sell,
|
||||
models.Donate,
|
||||
models.Rent,
|
||||
models.DisposeProduct
|
||||
@pytest.mark.parametrize('event_model_state', [
|
||||
(models.Sell, states.Trading.Sold),
|
||||
(models.Donate, states.Trading.Donated),
|
||||
(models.Rent, states.Trading.Renting),
|
||||
(models.DisposeProduct, states.Trading.ProductDisposed)
|
||||
])
|
||||
def test_trade(event_model: models.Event, user: UserClient):
|
||||
"""Tests POSTing all generic events."""
|
||||
def test_trade(event_model_state: Tuple[models.Event, states.Trading], user: UserClient):
|
||||
"""Tests POSTing all Trade events."""
|
||||
event_model, state = event_model_state
|
||||
snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
|
||||
event = {
|
||||
'type': event_model.t,
|
||||
|
@ -246,6 +254,7 @@ def test_trade(event_model: models.Event, user: UserClient):
|
|||
assert event['devices'][0]['id'] == snapshot['device']['id']
|
||||
device, _ = user.get(res=Device, item=snapshot['device']['id'])
|
||||
assert device['events'][-1]['id'] == event['id']
|
||||
assert device['trading'] == state.name
|
||||
|
||||
|
||||
@pytest.mark.xfail(reson='Develop migrate')
|
||||
|
@ -257,8 +266,9 @@ def test_migrate():
|
|||
def test_price_custom():
|
||||
computer = Desktop(serial_number='sn1', model='ml1', manufacturer='mr1',
|
||||
chassis=ComputerChassis.Docking)
|
||||
price = models.Price(price=25.25, currency=Currency.EUR)
|
||||
price = models.Price(price=Decimal(25.25), currency=Currency.EUR)
|
||||
price.device = computer
|
||||
assert computer.price == price
|
||||
db.session.add(computer)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -268,3 +278,15 @@ def test_price_custom():
|
|||
assert p['device']['id'] == price.device.id == computer.id
|
||||
assert p['price'] == 25.25
|
||||
assert p['currency'] == Currency.EUR.name == 'EUR'
|
||||
|
||||
c, _ = client.get(res=Device, item=computer.id)
|
||||
assert c['price']['id'] == p['id']
|
||||
|
||||
|
||||
@pytest.mark.xfail(reson='Develop test')
|
||||
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
|
||||
|
|
|
@ -254,7 +254,7 @@ def test_post_get_lot(user: UserClient):
|
|||
assert not l['children']
|
||||
|
||||
|
||||
def test_post_add_children_view_ui_tree_normal(user: UserClient):
|
||||
def test_lot_post_add_children_view_ui_tree_normal(user: UserClient):
|
||||
"""Tests adding children lots to a lot through the view and
|
||||
GETting the results."""
|
||||
parent, _ = user.post(({'name': 'Parent'}), res=Lot)
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
from decimal import Decimal
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
import pytest
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device.models import Computer, Desktop
|
||||
from ereuse_devicehub.resources.enums import Bios, ComputerChassis, ImageMimeTypes, Orientation, \
|
||||
RatingSoftware
|
||||
from ereuse_devicehub.resources.event.models import PhotoboxRate, WorkbenchRate
|
||||
from ereuse_devicehub.resources.image.models import Image, ImageList
|
||||
from ereuse_devicehub.resources.device.models import Computer, Desktop, HardDrive, Processor, \
|
||||
RamModule
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ComputerChassis, \
|
||||
FunctionalityRange, RatingSoftware
|
||||
from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkDataStorage, \
|
||||
BenchmarkProcessor, EreusePrice, WorkbenchRate
|
||||
from ereuse_devicehub.resources.event.rate import main
|
||||
from tests import conftest
|
||||
|
||||
|
||||
|
@ -15,7 +18,7 @@ from tests import conftest
|
|||
def test_workbench_rate_db():
|
||||
rate = WorkbenchRate(processor=0.1,
|
||||
ram=1.0,
|
||||
bios=Bios.A,
|
||||
bios_range=Bios.A,
|
||||
labelling=False,
|
||||
graphic_card=0.1,
|
||||
data_storage=4.1,
|
||||
|
@ -26,17 +29,63 @@ def test_workbench_rate_db():
|
|||
db.session.commit()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_photobox_rate_db():
|
||||
pc = Desktop(serial_number='24', chassis=ComputerChassis.Tower)
|
||||
image = Image(name='foo',
|
||||
content=b'123',
|
||||
file_format=ImageMimeTypes.jpg,
|
||||
orientation=Orientation.Horizontal,
|
||||
image_list=ImageList(device=pc))
|
||||
rate = PhotoboxRate(image=image,
|
||||
software=RatingSoftware.ECost,
|
||||
version=StrictVersion('1.0'),
|
||||
device=pc)
|
||||
db.session.add(rate)
|
||||
db.session.commit()
|
||||
@pytest.mark.xfail(reason='AggreagteRate only takes data from WorkbenchRate as for now')
|
||||
def test_rate_workbench_then_manual():
|
||||
"""Checks that a new AggregateRate is generated with a new rate
|
||||
value when a ManualRate is performed after performing a
|
||||
WorkbenchRate.
|
||||
|
||||
The new AggregateRate needs to be computed by the values of
|
||||
the WorkbenchRate + new values from ManualRate.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_rate():
|
||||
"""Test generating an AggregateRate for a given PC / components /
|
||||
WorkbenchRate ensuring results and relationships between
|
||||
pc - rate - workbenchRate - price.
|
||||
"""
|
||||
rate = WorkbenchRate(
|
||||
appearance_range=AppearanceRange.A,
|
||||
functionality_range=FunctionalityRange.A
|
||||
)
|
||||
pc = Desktop()
|
||||
hdd = HardDrive(size=476940)
|
||||
hdd.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
|
||||
cpu = Processor(cores=2, speed=3.4)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
|
||||
pc.components |= {
|
||||
hdd,
|
||||
RamModule(size=4096, speed=1600),
|
||||
RamModule(size=2048, speed=1067),
|
||||
cpu
|
||||
}
|
||||
rate.device = pc
|
||||
events = main.main(rate, RatingSoftware.ECost, StrictVersion('1.0'))
|
||||
price = next(e for e in events if isinstance(e, EreusePrice))
|
||||
assert price.price == Decimal('92.2001')
|
||||
assert price.retailer.standard.amount == Decimal('40.9714')
|
||||
assert price.platform.standard.amount == Decimal('18.8434')
|
||||
assert price.refurbisher.standard.amount == Decimal('32.3853')
|
||||
assert price.price >= price.retailer.standard.amount \
|
||||
+ price.platform.standard.amount \
|
||||
+ price.refurbisher.standard.amount
|
||||
assert price.retailer.warranty2.amount == Decimal('55.3085')
|
||||
assert price.platform.warranty2.amount == Decimal('25.4357')
|
||||
assert price.refurbisher.warranty2.amount == Decimal('43.7259')
|
||||
assert price.warranty2 == Decimal('124.47')
|
||||
# Checks relationships
|
||||
workbench_rate = next(e for e in events if isinstance(e, WorkbenchRate))
|
||||
aggregate_rate = next(e for e in events if isinstance(e, AggregateRate))
|
||||
assert price.rating == aggregate_rate
|
||||
assert aggregate_rate.workbench == workbench_rate
|
||||
assert aggregate_rate.rating == workbench_rate.rating == 4.61
|
||||
assert aggregate_rate.software == workbench_rate.software == RatingSoftware.ECost
|
||||
assert aggregate_rate.version == StrictVersion('1.0')
|
||||
assert aggregate_rate.appearance == workbench_rate.appearance
|
||||
assert aggregate_rate.functionality == workbench_rate.functionality
|
||||
assert aggregate_rate.rating_range == workbench_rate.rating_range
|
||||
assert cpu.rate == pc.rate == hdd.rate == aggregate_rate
|
||||
assert cpu.price == pc.price == aggregate_rate.price == hdd.price == price
|
||||
|
|
|
@ -0,0 +1,417 @@
|
|||
"""
|
||||
Tests of compute rating for every component in a Device
|
||||
Rates test done:
|
||||
-DataStorage
|
||||
-RamModule
|
||||
-Processor
|
||||
|
||||
Excluded cases in tests
|
||||
|
||||
- No Processor
|
||||
-
|
||||
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from ereuse_devicehub.resources.device.models import Desktop, HardDrive, Processor, RamModule
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, FunctionalityRange
|
||||
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
|
||||
WorkbenchRate
|
||||
from ereuse_devicehub.resources.event.rate.workbench.v1_0 import DataStorageRate, ProcessorRate, \
|
||||
RamRate, Rate
|
||||
|
||||
|
||||
def test_rate_data_storage_rate():
|
||||
"""
|
||||
Test to check if compute data storage rate have same value than previous score version;
|
||||
id = pc_1193, pc_1201, pc_79, pc_798
|
||||
"""
|
||||
|
||||
hdd_1969 = HardDrive(size=476940)
|
||||
hdd_1969.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
|
||||
|
||||
data_storage_rate = DataStorageRate().compute([hdd_1969], WorkbenchRate())
|
||||
|
||||
assert round(data_storage_rate, 2) == 4.02, 'DataStorageRate returns incorrect value(rate)'
|
||||
|
||||
hdd_3054 = HardDrive(size=476940)
|
||||
hdd_3054.events_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7))
|
||||
|
||||
# calculate DataStorage Rate
|
||||
data_storage_rate = DataStorageRate().compute([hdd_3054], WorkbenchRate())
|
||||
|
||||
assert round(data_storage_rate, 2) == 4.07, 'DataStorageRate returns incorrect value(rate)'
|
||||
|
||||
hdd_81 = HardDrive(size=76319)
|
||||
hdd_81.events_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3))
|
||||
|
||||
data_storage_rate = DataStorageRate().compute([hdd_81], WorkbenchRate())
|
||||
|
||||
assert round(data_storage_rate, 2) == 2.61, 'DataStorageRate returns incorrect value(rate)'
|
||||
|
||||
hdd_1556 = HardDrive(size=152587)
|
||||
hdd_1556.events_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4))
|
||||
|
||||
data_storage_rate = DataStorageRate().compute([hdd_1556], WorkbenchRate())
|
||||
|
||||
assert round(data_storage_rate, 2) == 3.70, 'DataStorageRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_data_storage_size_is_null():
|
||||
"""
|
||||
Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
|
||||
BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected;
|
||||
id = pc_2992
|
||||
"""
|
||||
|
||||
hdd_null = HardDrive(size=None)
|
||||
hdd_null.events_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
|
||||
|
||||
data_storage_rate = DataStorageRate().compute([hdd_null], WorkbenchRate())
|
||||
assert data_storage_rate is None
|
||||
|
||||
|
||||
def test_rate_no_data_storage():
|
||||
"""
|
||||
Test without data storage devices
|
||||
"""
|
||||
hdd_null = HardDrive()
|
||||
hdd_null.events_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
|
||||
data_storage_rate = DataStorageRate().compute([hdd_null], WorkbenchRate())
|
||||
assert data_storage_rate is None
|
||||
|
||||
|
||||
# RAM MODULE DEVICE TEST
|
||||
|
||||
|
||||
def test_rate_ram_rate():
|
||||
"""
|
||||
Test to check if compute ram rate have same value than previous score version
|
||||
only with 1 RamModule; id = pc_1201
|
||||
"""
|
||||
|
||||
ram1 = RamModule(size=2048, speed=1333)
|
||||
|
||||
ram_rate = RamRate().compute([ram1], WorkbenchRate())
|
||||
|
||||
assert round(ram_rate, 2) == 2.02, 'RamRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_ram_rate_2modules():
|
||||
"""
|
||||
Test to check if compute ram rate have same value than previous score version
|
||||
with 2 RamModule; id = pc_1193
|
||||
"""
|
||||
|
||||
ram1 = RamModule(size=4096, speed=1600)
|
||||
ram2 = RamModule(size=2048, speed=1067)
|
||||
|
||||
ram_rate = RamRate().compute([ram1, ram2], WorkbenchRate())
|
||||
|
||||
assert round(ram_rate, 2) == 3.79, 'RamRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_ram_rate_4modules():
|
||||
"""
|
||||
Test to check if compute ram rate have same value than previous score version
|
||||
with 2 RamModule; id = pc_79
|
||||
"""
|
||||
|
||||
ram1 = RamModule(size=512, speed=667)
|
||||
ram2 = RamModule(size=512, speed=800)
|
||||
ram3 = RamModule(size=512, speed=667)
|
||||
ram4 = RamModule(size=512, speed=533)
|
||||
|
||||
ram_rate = RamRate().compute([ram1, ram2, ram3, ram4], WorkbenchRate())
|
||||
|
||||
assert round(ram_rate, 2) == 1.99, 'RamRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_ram_module_size_is_0():
|
||||
"""
|
||||
Test where input data RamModule.size = 0; is like no RamModule has been detected; id = pc_798
|
||||
"""
|
||||
|
||||
ram0 = RamModule(size=0, speed=888)
|
||||
|
||||
ram_rate = RamRate().compute([ram0], WorkbenchRate())
|
||||
assert ram_rate is None
|
||||
|
||||
|
||||
def test_rate_ram_speed_is_null():
|
||||
"""
|
||||
Test where RamModule.speed is NULL (not detected) but has size.
|
||||
Pc ID = 795(1542), 745(1535), 804(1549)
|
||||
"""
|
||||
|
||||
ram0 = RamModule(size=2048, speed=None)
|
||||
|
||||
ram_rate = RamRate().compute([ram0], WorkbenchRate())
|
||||
|
||||
assert round(ram_rate, 2) == 1.85, 'RamRate returns incorrect value(rate)'
|
||||
|
||||
ram0 = RamModule(size=1024, speed=None)
|
||||
|
||||
ram_rate = RamRate().compute([ram0], WorkbenchRate())
|
||||
|
||||
assert round(ram_rate, 2) == 1.25, 'RamRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_no_ram_module():
|
||||
"""
|
||||
Test without RamModule
|
||||
"""
|
||||
ram0 = RamModule()
|
||||
|
||||
ram_rate = RamRate().compute([ram0], WorkbenchRate())
|
||||
assert ram_rate is None
|
||||
|
||||
|
||||
# PROCESSOR DEVICE TEST
|
||||
|
||||
def test_rate_processor_rate():
|
||||
"""
|
||||
Test to check if compute processor rate have same value than previous score version
|
||||
only with 1 core; id = 79
|
||||
"""
|
||||
|
||||
cpu = Processor(cores=1, speed=1.6)
|
||||
# add score processor benchmark
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
|
||||
|
||||
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
|
||||
|
||||
assert processor_rate == 1, 'ProcessorRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_processor_rate_2cores():
|
||||
"""
|
||||
Test to check if compute processor rate have same value than previous score version
|
||||
with 2 cores; id = pc_1193, pc_1201
|
||||
"""
|
||||
|
||||
cpu = Processor(cores=2, speed=3.4)
|
||||
# add score processor benchmark
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
|
||||
|
||||
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
|
||||
|
||||
assert round(processor_rate, 2) == 3.95, 'ProcessorRate returns incorrect value(rate)'
|
||||
|
||||
cpu = Processor(cores=2, speed=3.3)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
|
||||
|
||||
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
|
||||
|
||||
assert round(processor_rate, 2) == 3.93, 'ProcessorRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Debug test')
|
||||
def test_rate_processor_with_null_cores():
|
||||
"""
|
||||
Test with processor device have null number of cores
|
||||
"""
|
||||
cpu = Processor(cores=None, speed=3.3)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=0))
|
||||
|
||||
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
|
||||
|
||||
assert processor_rate == 1, 'ProcessorRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Debug test')
|
||||
def test_rate_processor_with_null_speed():
|
||||
"""
|
||||
Test with processor device have null speed value
|
||||
"""
|
||||
cpu = Processor(cores=1, speed=None)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=0))
|
||||
|
||||
processor_rate = ProcessorRate().compute(cpu, WorkbenchRate())
|
||||
|
||||
assert processor_rate == 1.06, 'ProcessorRate returns incorrect value(rate)'
|
||||
|
||||
|
||||
def test_rate_computer_rate():
|
||||
""" Test rate v1
|
||||
|
||||
pc_1193 = Computer()
|
||||
price = 92.2
|
||||
# add components characteristics of pc with id = 1193
|
||||
hdd_1969 = HardDrive(size=476940)
|
||||
hdd_1969.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
|
||||
ram1 = RamModule(size=4096, speed=1600)
|
||||
ram2 = RamModule(size=2048, speed=1067)
|
||||
cpu = Processor(cores=2, speed=3.4)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
|
||||
pc_1193.components.add(hdd_1969, ram1, ram2, cpu)
|
||||
# add functionality and appearance range
|
||||
rate_pc_1193 = WorkbenchRate(appearance_range=AppearanceRange.A, functionality_range=FunctionalityRange.A)
|
||||
# add component rate
|
||||
HDD_rate = 4.02
|
||||
RAM_rate = 3.79
|
||||
Processor_rate = 3.95
|
||||
Rating = 4.61
|
||||
|
||||
pc_1201 = Computer()
|
||||
price = 69.6
|
||||
hdd_3054 = HardDrive(size=476940)
|
||||
hdd_3054.events_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7))
|
||||
ram1 = RamModule(size=2048, speed=1333)
|
||||
cpu = Processor(cores=2, speed=3.3)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
|
||||
pc_1201.components.add(hdd_3054, ram1, cpu)
|
||||
# add functionality and appearance range
|
||||
rate_pc_1201 = WorkbenchRate(appearance_range=AppearanceRange.B, functionality_range=FunctionalityRange.A)
|
||||
# add component rate
|
||||
HDD_rate = 4.07
|
||||
RAM_rate = 2.02
|
||||
Processor_rate = 3.93
|
||||
Rating = 3.48
|
||||
|
||||
pc_79 = Computer()
|
||||
price = VeryLow
|
||||
hdd_81 = HardDrive(size=76319)
|
||||
hdd_81.events_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3))
|
||||
ram1 = RamModule(size=512, speed=667)
|
||||
ram2 = RamModule(size=512, speed=800)
|
||||
ram3 = RamModule(size=512, speed=667)
|
||||
ram4 = RamModule(size=512, speed=533)
|
||||
cpu = Processor(cores=1, speed=1.6)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
|
||||
pc_79.components.add(hdd_81, ram1, ram2, ram3, ram4, cpu)
|
||||
# add functionality and appearance range
|
||||
rate_pc_79 = WorkbenchRate(appearance_range=AppearanceRange.C, functionality_range=FunctionalityRange.A)
|
||||
# add component rate
|
||||
HDD_rate = 2.61
|
||||
RAM_rate = 1.99
|
||||
Processor_rate = 1
|
||||
Rating = 1.58
|
||||
|
||||
pc_798 = Computer()
|
||||
price = 50
|
||||
hdd_1556 = HardDrive(size=152587)
|
||||
hdd_1556.events_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4))
|
||||
ram0 = RamModule(size=0, speed=None)
|
||||
cpu = Processor(cores=2, speed=2.5)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=9974.3))
|
||||
pc_798.components.add(hdd_1556, ram0, cpu)
|
||||
# add functionality and appearance range
|
||||
rate_pc_798 = WorkbenchRate(appearance_range=AppearanceRange.B, functionality_range=FunctionalityRange.A)
|
||||
# add component rate
|
||||
HDD_rate = 3.7
|
||||
RAM_rate = 1
|
||||
Processor_rate = 4.09
|
||||
Rating = 2.5
|
||||
"""
|
||||
|
||||
# Create a new Computer with components characteristics of pc with id = 1193
|
||||
pc_test = Desktop()
|
||||
data_storage = HardDrive(size=476940)
|
||||
data_storage.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
|
||||
cpu = Processor(cores=2, speed=3.4)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
|
||||
pc_test.components |= {
|
||||
data_storage,
|
||||
RamModule(size=4096, speed=1600),
|
||||
RamModule(size=2048, speed=1067),
|
||||
cpu
|
||||
}
|
||||
# add functionality and appearance range
|
||||
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.A,
|
||||
functionality_range=FunctionalityRange.A)
|
||||
# Compute all components rates and general rating
|
||||
Rate().compute(pc_test, rate_pc)
|
||||
|
||||
assert round(rate_pc.ram, 2) == 3.79
|
||||
|
||||
assert round(rate_pc.data_storage, 2) == 4.02
|
||||
|
||||
assert round(rate_pc.processor, 2) == 3.95
|
||||
|
||||
assert round(rate_pc.rating, 2) == 4.61
|
||||
|
||||
# Create a new Computer with components characteristics of pc with id = 1201
|
||||
pc_test = Desktop()
|
||||
data_storage = HardDrive(size=476940)
|
||||
data_storage.events_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7))
|
||||
cpu = Processor(cores=2, speed=3.3)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
|
||||
pc_test.components |= {
|
||||
data_storage,
|
||||
RamModule(size=2048, speed=1333),
|
||||
cpu
|
||||
}
|
||||
# add functionality and appearance range
|
||||
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.B,
|
||||
functionality_range=FunctionalityRange.A)
|
||||
# Compute all components rates and general rating
|
||||
Rate().compute(pc_test, rate_pc)
|
||||
|
||||
assert round(rate_pc.ram, 2) == 2.02
|
||||
|
||||
assert round(rate_pc.data_storage, 2) == 4.07
|
||||
|
||||
assert round(rate_pc.processor, 2) == 3.93
|
||||
|
||||
assert round(rate_pc.rating, 2) == 3.48
|
||||
|
||||
# Create a new Computer with components characteristics of pc with id = 79
|
||||
pc_test = Desktop()
|
||||
data_storage = HardDrive(size=76319)
|
||||
data_storage.events_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3))
|
||||
cpu = Processor(cores=1, speed=1.6)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
|
||||
pc_test.components |= {
|
||||
data_storage,
|
||||
RamModule(size=512, speed=667),
|
||||
RamModule(size=512, speed=800),
|
||||
RamModule(size=512, speed=667),
|
||||
RamModule(size=512, speed=533),
|
||||
cpu
|
||||
}
|
||||
# add functionality and appearance range
|
||||
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.C,
|
||||
functionality_range=FunctionalityRange.A)
|
||||
# Compute all components rates and general rating
|
||||
Rate().compute(pc_test, rate_pc)
|
||||
|
||||
assert round(rate_pc.ram, 2) == 1.99
|
||||
|
||||
assert round(rate_pc.data_storage, 2) == 2.61
|
||||
|
||||
assert round(rate_pc.processor, 2) == 1
|
||||
|
||||
assert round(rate_pc.rating, 2) == 1.58
|
||||
|
||||
# Create a new Computer with components characteristics of pc with id = 798
|
||||
pc_test = Desktop()
|
||||
data_storage = HardDrive(size=152587)
|
||||
data_storage.events_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4))
|
||||
cpu = Processor(cores=2, speed=2.5)
|
||||
cpu.events_one.add(BenchmarkProcessor(rate=9974.3))
|
||||
pc_test.components |= {
|
||||
data_storage,
|
||||
RamModule(size=0, speed=None),
|
||||
cpu
|
||||
}
|
||||
# add functionality and appearance range
|
||||
rate_pc = WorkbenchRate(appearance_range=AppearanceRange.B,
|
||||
functionality_range=FunctionalityRange.A)
|
||||
# Compute all components rates and general rating
|
||||
Rate().compute(pc_test, rate_pc)
|
||||
|
||||
assert round(rate_pc.ram, 2) == 1
|
||||
|
||||
assert round(rate_pc.data_storage, 2) == 3.7
|
||||
|
||||
assert round(rate_pc.processor, 2) == 4.09
|
||||
|
||||
assert round(rate_pc.rating, 2) == 2.5
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark')
|
||||
def test_rate_computer_with_data_storage_without_benchmark():
|
||||
"""For example if the data storage was introduced manually
|
||||
or comes from an old version without benchmark."""
|
|
@ -1,9 +1,9 @@
|
|||
from datetime import datetime, timedelta, timezone
|
||||
from distutils.version import StrictVersion
|
||||
from typing import List, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from boltons import urlutils
|
||||
from teal.db import UniqueViolation
|
||||
from teal.marshmallow import ValidationError
|
||||
|
||||
|
@ -13,8 +13,7 @@ from ereuse_devicehub.devicehub import Devicehub
|
|||
from ereuse_devicehub.resources.device import models as m
|
||||
from ereuse_devicehub.resources.device.exceptions import NeedsId
|
||||
from ereuse_devicehub.resources.device.sync import MismatchBetweenTagsAndHid
|
||||
from ereuse_devicehub.resources.enums import Bios, ComputerChassis, RatingSoftware, \
|
||||
SnapshotSoftware
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkProcessor, \
|
||||
EraseSectors, Event, Snapshot, SnapshotRequest, WorkbenchRate
|
||||
from ereuse_devicehub.resources.tag import Tag
|
||||
|
@ -37,21 +36,11 @@ def test_snapshot_model():
|
|||
elapsed=timedelta(seconds=25))
|
||||
snapshot.device = device
|
||||
snapshot.request = SnapshotRequest(request={'foo': 'bar'})
|
||||
snapshot.events.add(WorkbenchRate(processor=0.1,
|
||||
ram=1.0,
|
||||
bios=Bios.A,
|
||||
labelling=False,
|
||||
graphic_card=0.1,
|
||||
data_storage=4.1,
|
||||
software=RatingSoftware.ECost,
|
||||
version=StrictVersion('1.0'),
|
||||
device=device))
|
||||
db.session.add(snapshot)
|
||||
db.session.commit()
|
||||
device = m.Desktop.query.one() # type: m.Desktop
|
||||
e1, e2 = device.events
|
||||
e1 = device.events[0]
|
||||
assert isinstance(e1, Snapshot), 'Creation order must be preserved: 1. snapshot, 2. WR'
|
||||
assert isinstance(e2, WorkbenchRate)
|
||||
db.session.delete(device)
|
||||
db.session.commit()
|
||||
assert Snapshot.query.one_or_none() is None
|
||||
|
@ -59,6 +48,8 @@ def test_snapshot_model():
|
|||
assert User.query.one() is not None
|
||||
assert m.Desktop.query.one_or_none() is None
|
||||
assert m.Device.query.one_or_none() is None
|
||||
# Check properties
|
||||
assert device.url == urlutils.URL('http://localhost/devices/1')
|
||||
|
||||
|
||||
def test_snapshot_schema(app: Devicehub):
|
||||
|
@ -321,27 +312,37 @@ def test_erase(user: UserClient):
|
|||
assert step['type'] == 'StepZero'
|
||||
assert step['error'] is False
|
||||
assert 'num' not in step
|
||||
assert storage['privacy'] == erasure['device']['privacy'] == 'EraseSectors'
|
||||
|
||||
# Let's try a second erasure with an error
|
||||
s['uuid'] = uuid4()
|
||||
s['components'][0]['events'][0]['error'] = True
|
||||
snapshot, _ = user.post(s, res=Snapshot)
|
||||
assert snapshot['components'][0]['hid'] == 'c1mr-c1s-c1ml'
|
||||
assert snapshot['components'][0]['privacy'] == 'EraseSectorsError'
|
||||
|
||||
|
||||
def test_snapshot_computer_monitor(user: UserClient):
|
||||
s = file('computer-monitor.snapshot')
|
||||
snapshot_and_check(user, s, event_types=('AppRate',))
|
||||
snapshot_and_check(user, s, event_types=('ManualRate',))
|
||||
# todo check that ManualRate has generated an AggregateRate
|
||||
|
||||
|
||||
def test_snapshot_mobile_smartphone(user: UserClient):
|
||||
s = file('smartphone.snapshot')
|
||||
snapshot_and_check(user, s, event_types=('AppRate',))
|
||||
snapshot_and_check(user, s, event_types=('ManualRate',))
|
||||
# todo check that ManualRate has generated an AggregateRate
|
||||
|
||||
|
||||
@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 test
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Test not developed')
|
||||
def test_snapshot_components_empty():
|
||||
"""
|
||||
Tests that a snapshot whose components are an empty list remove
|
||||
|
|
|
@ -12,10 +12,12 @@ from ereuse_devicehub.devicehub import Devicehub
|
|||
from ereuse_devicehub.resources.agent.models import Organization
|
||||
from ereuse_devicehub.resources.device.models import Desktop, Device
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis
|
||||
from ereuse_devicehub.resources.event.models import Snapshot
|
||||
from ereuse_devicehub.resources.tag import Tag
|
||||
from ereuse_devicehub.resources.tag.view import CannotCreateETag, LinkedToAnotherDevice, \
|
||||
TagNotLinked
|
||||
from tests import conftest
|
||||
from tests.conftest import file
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
|
@ -179,8 +181,10 @@ def test_tag_manual_link(app: Devicehub, user: UserClient):
|
|||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_tag_secondary():
|
||||
"""Creates and consumes tags with a secondary id."""
|
||||
def test_tag_secondary_workbench_link_find(user: UserClient):
|
||||
"""Creates and consumes tags with a secondary id, linking them
|
||||
through Workbench to a device
|
||||
and getting them through search."""
|
||||
t = Tag('foo', secondary='bar')
|
||||
db.session.add(t)
|
||||
db.session.flush()
|
||||
|
@ -189,6 +193,18 @@ def test_tag_secondary():
|
|||
with pytest.raises(ResourceNotFound):
|
||||
Tag.from_an_id('nope').one()
|
||||
|
||||
s = file('basic.snapshot')
|
||||
s['device']['tags'] = [{'id': 'foo', 'secondary': 'bar', 'type': 'Tag'}]
|
||||
snapshot, _ = user.post(s, res=Snapshot)
|
||||
device, _ = user.get(res=Device, item=snapshot['device']['id'])
|
||||
assert device['tags'][0]['id'] == 'foo'
|
||||
assert device['tags'][0]['secondary'] == 'bar'
|
||||
|
||||
r, _ = user.get(res=Device, query=[('search', 'foo'), ('filter', {'type': ['Computer']})])
|
||||
assert len(r['items']) == 1
|
||||
r, _ = user.get(res=Device, query=[('search', 'bar'), ('filter', {'type': ['Computer']})])
|
||||
assert len(r['items']) == 1
|
||||
|
||||
|
||||
def test_tag_create_tags_cli_csv(app: Devicehub, user: UserClient):
|
||||
"""Checks creating tags with the CLI endpoint using a CSV."""
|
||||
|
|
|
@ -27,7 +27,7 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
file('workbench-server-3.erase'),
|
||||
file('workbench-server-4.install')
|
||||
))
|
||||
s['components'][5]['events'] = [file('workbench-server-3.erase')]
|
||||
s['components'][5]['events'].append(file('workbench-server-3.erase'))
|
||||
# Create tags
|
||||
for t in s['device']['tags']:
|
||||
user.post({'id': t['id']}, res=Tag)
|
||||
|
@ -35,7 +35,7 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||
events = snapshot['events']
|
||||
assert {(event['type'], event['device']) for event in events} == {
|
||||
# todo missing Rate event aggregating the rates
|
||||
('AggregateRate', 1),
|
||||
('WorkbenchRate', 1),
|
||||
('BenchmarkProcessorSysbench', 5),
|
||||
('StressTest', 1),
|
||||
|
@ -45,10 +45,26 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
('Install', 6),
|
||||
('EraseSectors', 7),
|
||||
('BenchmarkDataStorage', 6),
|
||||
('BenchmarkDataStorage', 7),
|
||||
('TestDataStorage', 6)
|
||||
}
|
||||
assert snapshot['closed']
|
||||
assert not snapshot['error']
|
||||
device, _ = user.get(res=Device, item=snapshot['device']['id'])
|
||||
assert device['dataStorageSize'] == 1100
|
||||
assert device['chassis'] == 'Tower'
|
||||
assert device['hid'] == 'd1mr-d1s-d1ml'
|
||||
assert device['graphicCardModel'] == device['components'][0]['model'] == 'gc1-1ml'
|
||||
assert device['networkSpeeds'] == [1000, 58]
|
||||
assert device['processorModel'] == device['components'][3]['model'] == 'p1-1ml'
|
||||
assert device['ramSize'] == 2048, 'There are 3 RAM: 2 x 1024 and 1 None sizes'
|
||||
assert device['rate']['closed']
|
||||
assert not device['rate']['error']
|
||||
assert device['rate']['rating'] == 0
|
||||
assert device['rate']['workbench']
|
||||
assert device['rate']['appearanceRange'] == 'A'
|
||||
assert device['rate']['functionalityRange'] == 'B'
|
||||
assert device['tags'][0]['id'] == 'tag1'
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Functionality not yet developed.')
|
||||
|
@ -122,8 +138,9 @@ def test_workbench_server_phases(user: UserClient):
|
|||
def test_real_hp_11(user: UserClient):
|
||||
s = file('real-hp.snapshot.11')
|
||||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||
assert snapshot['device']['hid'] == 'hewlett-packard-czc0408yjg-hp_compaq_8100_elite_sff'
|
||||
assert snapshot['device']['chassis'] == 'Tower'
|
||||
pc = snapshot['device']
|
||||
assert pc['hid'] == 'hewlett-packard-czc0408yjg-hp_compaq_8100_elite_sff'
|
||||
assert pc['chassis'] == 'Tower'
|
||||
assert set(e['type'] for e in snapshot['events']) == {
|
||||
'BenchmarkDataStorage',
|
||||
'BenchmarkProcessor',
|
||||
|
@ -133,6 +150,10 @@ def test_real_hp_11(user: UserClient):
|
|||
'StressTest'
|
||||
}
|
||||
assert len(list(e['type'] for e in snapshot['events'])) == 6
|
||||
assert pc['networkSpeeds'] == [1000, None], 'Device has no WiFi'
|
||||
assert pc['processorModel'] == 'intel core i3 cpu 530 @ 2.93ghz'
|
||||
assert pc['ramSize'] == 8192
|
||||
assert pc['dataStorageSize'] == 305245
|
||||
|
||||
|
||||
def test_real_toshiba_11(user: UserClient):
|
||||
|
@ -140,7 +161,7 @@ def test_real_toshiba_11(user: UserClient):
|
|||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||
|
||||
|
||||
def test_real_eee_1001pxd(user: UserClient):
|
||||
def test_snapshot_real_eee_1001pxd(user: UserClient):
|
||||
"""
|
||||
Checks the values of the device, components,
|
||||
events and their relationships of a real pc.
|
||||
|
@ -155,6 +176,7 @@ def test_real_eee_1001pxd(user: UserClient):
|
|||
assert pc['manufacturer'] == 'asustek computer inc.'
|
||||
assert pc['hid'] == 'asustek_computer_inc-b8oaas048286-1001pxd'
|
||||
assert pc['tags'] == []
|
||||
assert pc['networkSpeeds'] == [100, 0], 'Although it has WiFi we do not know the speed'
|
||||
components = snapshot['components']
|
||||
wifi = components[0]
|
||||
assert wifi['hid'] == 'qualcomm_atheros-74_2f_68_8b_fd_c8-ar9285_wireless_network_adapter'
|
||||
|
@ -170,7 +192,7 @@ def test_real_eee_1001pxd(user: UserClient):
|
|||
assert cpu['threads'] == 1
|
||||
assert cpu['speed'] == 1.667
|
||||
assert 'hid' not in cpu
|
||||
assert cpu['model'] == 'intel atom cpu n455 @ 1.66ghz'
|
||||
assert pc['processorModel'] == cpu['model'] == 'intel atom cpu n455 @ 1.66ghz'
|
||||
cpu, _ = user.get(res=Device, item=cpu['id'])
|
||||
events = cpu['events']
|
||||
sysbench = next(e for e in events if e['type'] == em.BenchmarkProcessorSysbench.t)
|
||||
|
@ -204,6 +226,7 @@ def test_real_eee_1001pxd(user: UserClient):
|
|||
ram = components[6]
|
||||
assert ram['interface'] == 'DDR2'
|
||||
assert ram['speed'] == 667
|
||||
assert pc['ramSize'] == ram['size'] == 1024
|
||||
hdd = components[7]
|
||||
assert hdd['type'] == 'HardDrive'
|
||||
assert hdd['hid'] == 'hitachi-e2024242cv86hj-hts54322'
|
||||
|
@ -223,6 +246,7 @@ def test_real_eee_1001pxd(user: UserClient):
|
|||
assert erase['startTime']
|
||||
assert erase['zeros'] is False
|
||||
assert erase['error'] is False
|
||||
assert hdd['privacy'] == 'EraseBasic'
|
||||
mother = components[8]
|
||||
assert mother['hid'] == 'asustek_computer_inc-eee0123456789-1001pxd'
|
||||
|
||||
|
@ -243,6 +267,11 @@ def test_real_eee_1000h(user: UserClient):
|
|||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='We do not have a snapshot file to use')
|
||||
def test_real_full_with_workbench_rate(user: UserClient):
|
||||
pass
|
||||
|
||||
|
||||
SNAPSHOTS_NEED_ID = {
|
||||
'box-xavier.snapshot.json',
|
||||
'custom.lshw.snapshot.json',
|
||||
|
@ -267,3 +296,9 @@ def test_workbench_fixtures(file: pathlib.Path, user: UserClient):
|
|||
user.post(res=em.Snapshot,
|
||||
data=s,
|
||||
status=201 if file.name not in SNAPSHOTS_NEED_ID else NeedsId)
|
||||
|
||||
|
||||
def test_workbench_asus_1001pxd_rate_low(user: UserClient):
|
||||
"""Tests an Asus 1001pxd with a low rate."""
|
||||
s = file('asus-1001pxd.snapshot')
|
||||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||
|
|
Reference in New Issue