Review rate changes

This commit is contained in:
Xavier Bustamante Talavera 2019-05-08 19:12:05 +02:00
parent e6d496872c
commit 16d00256df
40 changed files with 642 additions and 568 deletions

View File

@ -39,12 +39,6 @@ class DevicehubConfig(Config):
}
API_DOC_CLASS_DISCRIMINATOR = 'type'
# TODO is necessary??
WORKBENCH_RATE_VERSION = StrictVersion('1.0')
PHOTOBOX_RATE_VERSION = StrictVersion('1.0')
"""
Official versions for WorkbenchRate and PhotoboxRate
"""
PRICE_SOFTWARE = PriceSoftware.Ereuse
PRICE_VERSION = StrictVersion('1.0')
PRICE_CURRENCY = Currency.EUR

View File

@ -164,7 +164,7 @@
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
}
],
"manufacturer": "ASUSTeK Computer INC.",

View File

@ -16,7 +16,7 @@
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
}
],
"manufacturer": "ASUSTeK Computer INC."

View File

@ -11,7 +11,7 @@ device:
resolutionHeight: 1080
size: 21.5
events:
- type: TestVisual
- type: VisualTest
appearanceRange: A
functionalityRange: C
labelling: False

View File

@ -16,7 +16,7 @@
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
}
],
"type": "Desktop",

View File

@ -8,7 +8,7 @@ device:
manufacturer: BAZ
layout: ES
events:
- type: TestVisual
- type: VisualTest
appearanceRange: A
functionalityRange: A
labelling: False

View File

@ -18,7 +18,7 @@
{
"appearanceRange": "B",
"functionalityRange": "C",
"type": "TestVisual"
"type": "VisualTest"
}
],
"type": "Desktop",

View File

@ -122,7 +122,7 @@
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
}
],
"manufacturer": "Hewlett-Packard",

View File

@ -154,7 +154,7 @@
{
"appearanceRange": "B",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
}
]
},

View File

@ -157,7 +157,7 @@
{
"appearanceRange": "B",
"functionalityRange": "D",
"type": "TestVisual"
"type": "VisualTest"
}
],
"serialNumber": "CZC0408YJG",

View File

@ -8,7 +8,7 @@ device:
serialNumber: ABCDEF
imei: 35686800-004141-20
events:
- type: TestVisual
- type: VisualTest
appearanceRange: A
functionalityRange: B
labelling: False

View File

@ -18,90 +18,90 @@ device:
model: d1ml
manufacturer: d1mr
tags:
- type: Tag
id: tag1
- type: Tag
id: tag1
events:
- type: TestVisual
- type: VisualTest
appearanceRange: A
functionalityRange: B
- type: BenchmarkRamSysbench
rate: 2444
elapsed: 1
components:
- type: GraphicCard
serialNumber: gc1-1s
model: gc1-1ml
manufacturer: gc1-1mr
- type: RamModule
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-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
writeSpeed: 15
elapsed: 21
- type: TestDataStorage
elapsed: 233
severity: Info
status: Completed without error
length: Short
lifetime: 99
assessment: True
powerCycleCount: 11
reallocatedSectorCount: 2
reportedUncorrectableErrors: 1
commandTimeout: 11
currentPendingSectorCount: 1
offlineUncorrectable: 33
remainingLifetimePercentage: 1
- type: HardDrive
serialNumber: hdd1-1s
model: hdd1-1ml
manufacturer: hdd1-1mr
events:
- 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
- type: GraphicCard
serialNumber: gc1-1s
model: gc1-1ml
manufacturer: gc1-1mr
- type: RamModule
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-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
writeSpeed: 15
elapsed: 21
- type: TestDataStorage
elapsed: 233
severity: Info
status: Completed without error
length: Short
lifetime: 99
assessment: True
powerCycleCount: 11
reallocatedSectorCount: 2
reportedUncorrectableErrors: 1
commandTimeout: 11
currentPendingSectorCount: 1
offlineUncorrectable: 33
remainingLifetimePercentage: 1
- type: HardDrive
serialNumber: hdd1-1s
model: hdd1-1ml
manufacturer: hdd1-1mr
events:
- 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

View File

@ -422,8 +422,7 @@ class Computer(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.# TODO add all grade tables (chassis defects, camera defects, buttons test, connectivity, ..)
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.

View File

@ -42,7 +42,7 @@ class Device(Thing):
production_date = ... # type: Column
brand = ... # type: Column
generation = ... # type: Column
variant = ... # type: Column
variant = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
@ -65,7 +65,7 @@ class Device(Thing):
self.production_date = ... # type: Optional[datetime]
self.brand = ... # type: Optional[str]
self.generation = ... # type: Optional[int]
self.variant = ... # type: Optional[str]
self.variant = ... # type: Optional[str]
@property
def events(self) -> List[e.Event]:
@ -202,15 +202,15 @@ class TelevisionSet(Monitor):
class Mobile(Device):
imei = ... # type: Column
meid = ... # type: Column
ram_size = ... # type: Column
data_storage_size = ... # type: Column
ram_size = ... # type: Column
data_storage_size = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.imei = ... # type: Optional[int]
self.meid = ... # type: Optional[str]
self.ram_size = ... # type: Optional[int]
self.data_storage_size = ... # type: Optional[int]
self.ram_size = ... # type: Optional[int]
self.data_storage_size = ... # type: Optional[int]
class Smartphone(Mobile):

View File

@ -183,7 +183,6 @@ class Mobile(Device):
data_key='dataStorageSize',
description=m.Mobile.data_storage_size)
@pre_load
def convert_check_imei(self, data):
if data.get('imei', None):

View File

@ -1,18 +1,18 @@
{% macro component_type(components, type) %}
<ul>
{% for c in components if c.t == type %}
<li>
{{ c.__format__('t') }}
<p>
<small class="text-muted">{{ c.__format__('s') }}</small>
</p>
</li>
{% endfor %}
</ul>
<ul>
{% for c in components if c.t == type %}
<li>
{{ c.__format__('t') }}
<p>
<small class="text-muted">{{ c.__format__('s') }}</small>
</p>
</li>
{% endfor %}
</ul>
{% endmacro %}
{% macro rate(range) %}
<span class="label label-primary">
<span class="label label-primary">
{{ range }}
</span>
{% endmacro %}

View File

@ -31,9 +31,9 @@ class OfType(f.Str):
class RateQ(query.Query):
rating = query.Between(events.Rate.rating, f.Float())
appearance = query.Between(events.Rate.appearance, f.Float())
functionality = query.Between(events.Rate.functionality, f.Float())
rating = query.Between(events.Rate._rating, f.Float())
appearance = query.Between(events.Rate._appearance, f.Float())
functionality = query.Between(events.Rate._functionality, f.Float())
class TagQ(query.Query):

View File

@ -3,7 +3,8 @@ from collections import OrderedDict
from flask import current_app
from ereuse_devicehub.resources.device import models as d
from ereuse_devicehub.resources.event.models import TestDataStorage, BenchmarkDataStorage
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, RateComputer, \
TestDataStorage
class DeviceRow(OrderedDict):
@ -43,6 +44,7 @@ class DeviceRow(OrderedDict):
if rate:
self['Rate'] = rate.rating
self['Range'] = rate.rating_range
assert isinstance(rate, RateComputer)
self['Processor Rate'] = rate.processor
self['Processor Range'] = rate.processor_range
self['RAM Rate'] = rate.ram
@ -92,15 +94,18 @@ class DeviceRow(OrderedDict):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Privacy'.format(type, i)] = component.privacy
try:
self['{} {} Lifetime'.format(type, i)] = component.last_event_of(TestDataStorage).lifetime
self['{} {} Lifetime'.format(type, i)] = component.last_event_of(
TestDataStorage).lifetime
except:
self['{} {} Lifetime'.format(type, i)] = ''
try:
self['{} {} Reading speed'.format(type, i)] = component.last_event_of(BenchmarkDataStorage).read_speed
self['{} {} Reading speed'.format(type, i)] = component.last_event_of(
BenchmarkDataStorage).read_speed
except:
self['{} {} Reading speed'.format(type, i)] = ''
try:
self['{} {} Writing speed'.format(type, i)] = component.last_event_of(BenchmarkDataStorage).write_speed
self['{} {} Writing speed'.format(type, i)] = component.last_event_of(
BenchmarkDataStorage).write_speed
except:
self['{} {} Writing speed'.format(type, i)] = ''

View File

@ -109,7 +109,6 @@ class DevicesDocumentView(DeviceView):
query = self.query(args)
return self.generate_post_csv(query)
# TODO fix only put one row for device.t == computer (rewrite multiples_devices.csv)
def generate_post_csv(self, query):
"""
Get device query and put information in csv format
@ -122,9 +121,9 @@ class DevicesDocumentView(DeviceView):
for device in query:
d = DeviceRow(device)
if first:
cw.writerow(name for name in d.keys())
cw.writerow(d.keys())
first = False
cw.writerow(v for v in d.values())
cw.writerow(d.values())
output = make_response(data.getvalue())
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
output.headers['Content-type'] = 'text/csv'

View File

@ -1,5 +1,4 @@
from contextlib import suppress
from distutils.version import StrictVersion
from enum import Enum, IntEnum, unique
from typing import Set, Union
@ -18,8 +17,8 @@ class SnapshotSoftware(Enum):
return self.name
RATE_POSITIVE = 0, 10
RATE_NEGATIVE = -3, 5
R_POSITIVE = 0, 10
R_NEGATIVE = -3, 5
@unique
@ -65,45 +64,30 @@ class PriceSoftware(Enum):
Ereuse = 'Ereuse'
@unique
class AggregateRatingVersions(Enum):
v1 = StrictVersion('1.0')
"""
This version is set to aggregate :class:`ereuse_devicehub.resources.
event.models.RateComputer` version X and :class:`ereuse_devicehub.
resources.event.models.PhotoboxRate` version Y.
"""
@unique
class AppearanceRange(Enum):
"""
This grade will be defined based on the aesthetics/cosmetic aspects, like visual damage or blemishes principally
focused on chassis, physical buttons and screens.
"""
"""Grades the imperfections that aesthetically affect the device, but not its usage."""
Z = 'Z. The device is new'
A = 'A. Is like new; without visual damage'
B = 'B. Is in really good condition; small visual damage in difficult places to spot'
C = 'C. Is in good condition; small visual damage in parts that are easy to spot, minor cosmetic blemishes on chassis)'
C = 'C. Is in good condition; small visual damage in parts that are easy to spot, minor cosmetic blemishes on chassis'
D = 'D. Is acceptable; visual damage in visible parts, major cosmetic blemishes on chassis, missing cosmetic parts'
E = 'E. Is unacceptable; severity visual damage, missing essential parts'
NONE = 'NA. Grade doesnt exists'
E = 'E. Is unacceptable; considerable visual damage, missing essential parts'
APPEARANCE_RANGE = 0.5, -0.3
def __str__(self):
return self.name
@unique
class FunctionalityRange(Enum):
"""Based on usage condition of a device and its functionality aspects, like screen defect or camera defects"""
"""Grades the defects of a device that affect its usage."""
A = 'A. All the buttons works perfectly, no screen/camera defects and chassis without usage issues'
B = 'B. There is a button difficult to press or unstable it, a screen/camera defect or chassis problem'
C = 'C. Chassis defects or multiple buttons don\'t work; broken or unusable it, some screen/camera defect'
D = 'D. Chassis severity usage problems. All buttons, screen or camera don\'t work; broken or unusable it'
NONE = 'NA. Grade doesnt exists'
FUNCTIONALITY_RANGE = 0.4, -0.3
def __str__(self):
return self.name
@unique
@ -113,9 +97,11 @@ class BatteryHealthRange(Enum):
B = 'B. Battery health is good'
C = 'C. Battery health is overheat / over voltage status but can stand the minimum duration'
D = 'D. Battery health is bad; cant stand the minimum duration time'
E = 'E. Battery health is very bad; and status is dead; unusable or miss it '
E = 'E. Battery health is very bad; and status is dead; unusable or miss it'
NONE = 'NA. Grade doesnt exists'
def __str__(self):
return self.name
@unique
@ -127,6 +113,9 @@ class BiosAccessRange(Enum):
D = 'D. Like B or C, but you had to unlock the BIOS (i.e. by removing the battery)'
E = 'E. The device could not be booted through the network.'
def __str__(self):
return self.name
@unique
class Orientation(Enum):

View File

@ -109,11 +109,6 @@ class TestConnectivityDef(TestDef):
SCHEMA = schemas.TestConnectivity
class TestBatteryDef(TestDef):
VIEW = None
SCHEMA = schemas.TestBattery
class TestCameraDef(TestDef):
VIEW = None
SCHEMA = schemas.TestCamera
@ -134,9 +129,9 @@ class TestBiosDef(TestDef):
SCHEMA = schemas.TestBios
class TestVisualDef(TestDef):
class VisualTestDef(TestDef):
VIEW = None
SCHEMA = schemas.TestVisual
SCHEMA = schemas.VisualTest
class RateDef(EventDef):

View File

@ -14,7 +14,7 @@ from collections import Iterable
from contextlib import suppress
from datetime import datetime, timedelta
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_UP
from typing import Optional, Set, Union, Tuple
from typing import Optional, Set, Union
from uuid import uuid4
import inflection
@ -40,10 +40,10 @@ 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 BiosAccessRange, ErasureStandards, \
PhysicalErasureMethod, PriceSoftware, RATE_NEGATIVE, RATE_POSITIVE, \
RatingRange, ReceiverRole, Severity, SnapshotExpectedEvents, SnapshotSoftware, \
TestDataStorageLength, FunctionalityRange, AppearanceRange, BatteryHealthRange
from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, BiosAccessRange, \
ErasureStandards, FunctionalityRange, PhysicalErasureMethod, PriceSoftware, \
R_NEGATIVE, R_POSITIVE, RatingRange, ReceiverRole, Severity, SnapshotExpectedEvents, \
SnapshotSoftware, TestDataStorageLength
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
from ereuse_devicehub.resources.user.models import User
@ -669,7 +669,6 @@ class TestMixin:
return Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
class MeasureBattery(TestMixin, Test):
"""A sample of the status of the battery.
@ -708,7 +707,7 @@ class TestDataStorage(TestMixin, Test):
assessment = Column(Boolean)
reallocated_sector_count = Column(SmallInteger)
power_cycle_count = Column(SmallInteger)
reported_uncorrectable_errors = Column(SmallInteger)
_reported_uncorrectable_errors = Column('reported_uncorrectable_errors', Integer)
command_timeout = Column(Integer)
current_pending_sector_count = Column(SmallInteger)
offline_uncorrectable = Column(SmallInteger)
@ -737,6 +736,16 @@ class TestDataStorage(TestMixin, Test):
t += self.description
return t
@property
def reported_uncorrectable_errors(self):
return self._reported_uncorrectable_errors
@reported_uncorrectable_errors.setter
def reported_uncorrectable_errors(self, value):
# We assume that a huge number is not meaningful
# So we keep it like this
self._reported_uncorrectable_errors = min(value, db.PSQL_INT_MAX)
class StressTest(TestMixin, Test):
"""The act of stressing (putting to the maximum capacity)
@ -760,95 +769,84 @@ class TestAudio(TestMixin, Test):
"""
Test to check all this aspects related with audio functions, Manual Tests??
"""
loudspeaker = Column(Boolean)
loudspeaker.comment = 'Test to determine if the speaker is working properly and what sound quality it has.'
microphone = Column(Boolean)
microphone.comment = 'This evaluate if microphone works correctly'
_speaker = Column('speaker', Boolean)
_speaker.comment = """Whether the speaker works as expected."""
_microphone = Column('microphone', Boolean)
_microphone.comment = """Whether the microphone works as expected."""
@property
def speaker(self):
return self._speaker
@speaker.setter
def speaker(self, x):
self._speaker = x
self._check()
@property
def microphone(self):
return self._microphone
@microphone.setter
def microphone(self, x):
self._microphone = x
self._check()
def _check(self):
"""Sets ``severity`` to ``error`` if any of the variables fail."""
if not self._speaker or not self._microphone:
self.severity = Severity.Error
class TestConnectivity(TestMixin, Test):
"""
Test to check all this aspects related with functionality connections in devices
"""
# TODO Add Severity and return unique score
cellular_network = Column(Boolean)
cellular_network.comment = 'Evaluate if cellular network works properly'
wifi = Column(Boolean)
wifi.comment = 'Evaluate if wifi connection works correctly'
bluetooth = Column(Boolean)
bluetooth.comment = 'Evaluate if bluetooth works'
usb_port = Column(Boolean)
usb_port.comment = 'Evaluate if usb port was detected and charger plug works'
locked = Column(Boolean)
locked.comment = 'Test to check if devices is locked'
"""Tests that the device can connect both physically and
wirelessly.
class TestBattery(TestMixin, Test):
A failing test means that at least one connection of the device
is not working well. A comment should get into more detail.
"""
Test battery health, status and length of charge. Minimum X minutes discharging the device
"""
# TODO how to determinate if test PASS depend on battery stat and/or health
battery_stat = Column(Boolean)
battery_stat.comment = """
Some batteries can report a self-check life status.
"""
battery_health = Column(DBEnum(BatteryHealthRange))
battery_health.comment = BatteryHealthRange.__doc__
class TestCamera(TestMixin, Test):
"""Tests the working conditions of the camera of the device,
specially when taking pictures or recording video.
"""
Test to determinate functionality and defects on camera when take pictures or record video
# TODO define when test FAIL
"""
camera = Column(Boolean)
camera.comment = ""
class TestKeyboard(TestMixin, Test):
"""
Test to determinate if keyboard layout are and works correctly
# TODO define when test FAIL
"""
keyboard = Column(Boolean)
keyboard.comment = ""
"""Whether the keyboard works correctly."""
class TestTrackpad(TestMixin, Test):
"""
Test trackpad works correctly
# TODO define when test FAIL
"""
trackpad = Column(Boolean)
trackpad.comment = ""
"""Whether the trackpad works correctly."""
class TestBios(TestMixin, Test):
"""
Test that determinate motherboard no beeps, codes or errors when power on.
And a grade to reflect some possibles difficult to access or modify setting in the BIOS, like password protection..
"""
"""Tests the working condition and grades the usability of the BIOS."""
bios_power_on = Column(Boolean)
bios_power_on.comment = """
Motherboards do a self check when powering up (R2 p.23), test PASS if no beeps, codes, or errors appears.
bios_power_on.comment = """Whether there are no beeps or error
codes when booting up.
Reference: R2 standard page 23.
"""
access_range = Column(DBEnum(BiosAccessRange))
access_range.comment = 'Range of difficult to access BIOS'
access_range.comment = """Difficulty to modify the boot menu.
This is used as an usability measure for accessing and modifying
a bios, specially as something as important as modifying the boot
menu."""
class TestVisual(TestMixin, Test):
"""
Manual rate test its are represented with grade and focuses mainly on
the aesthetic or cosmetic defects of important parts of a device.
Like defects on chassis, display, ..
class VisualTest(TestMixin, Test):
"""The act of visually inspecting the appearance and functionality
of the device.
"""
appearance_range = Column(DBEnum(AppearanceRange))
appearance_range.comment = AppearanceRange.__doc__
functionality_range = Column(DBEnum(FunctionalityRange))
functionality_range.comment = FunctionalityRange.__doc__
labelling = Column(Boolean)
labelling.comment = """Whether there are tags to be removed.
"""
labelling.comment = """Whether there are tags to be removed."""
def __str__(self) -> str:
return super().__str__() + '. Appearance {} and functionality {}'.format(
@ -858,29 +856,54 @@ class TestVisual(TestMixin, Test):
class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
"""The act of computing a rate based on different categories:
1. Quality: the appearance, performance, and functionality
of a device.
There are two ways of rating a device:
1. When processing the device with Workbench and the Android App.
2. Anytime after with the Android App or website.X
"""The act of computing a rate based on different categories"""
# todo jn: explain in each comment what the rate considers.
N = 2
"""The number of significant digits for rates.
Values are rounded and stored to it.
"""
rating = Column(Float(decimal_return_scale=2), check_range('rating', *RATE_POSITIVE))
rating.comment = """The rating for the content."""
_rating = Column('rating', Float(decimal_return_scale=N), check_range('rating', *R_POSITIVE))
_rating.comment = """The rating for the content."""
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))
_appearance = Column('appearance',
Float(decimal_return_scale=N),
check_range('appearance', *R_NEGATIVE))
_appearance.comment = """"""
_functionality = Column('functionality',
Float(decimal_return_scale=N),
check_range('functionality', *R_NEGATIVE))
_functionality.comment = """"""
@property
def rating(self):
return self._rating
@rating.setter
def rating(self, x):
self._rating = round(max(x, 0), self.N)
@property
def appearance(self):
return self._appearance
@appearance.setter
def appearance(self, x):
self._appearance = round(x, self.N)
@property
def functionality(self):
return self._functionality
@functionality.setter
def functionality(self, x):
self._functionality = round(x, self.N)
@property
def rating_range(self) -> RatingRange:
if self.rating:
return RatingRange.from_score(self.rating)
""""""
return RatingRange.from_score(self.rating) if self.rating else None
@declared_attr
def __mapper_args__(cls):
@ -901,54 +924,82 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
@classmethod
def compute(cls, device) -> 'RateComputer':
"""
The act of compute general computer rate
"""
raise NotImplementedError()
class RateComputer(Rate):
"""
Main class to group by device type: Computer
Computer is broadly extended by ``Desktop``, ``Laptop``, and
``Server``.
"""
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
class RateMixin:
@declared_attr
def id(cls):
return Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE),
comment='Is a test explain cpu component.')
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE),
comment='RAM memory rate.')
data_storage = Column(Float(decimal_return_scale=2), check_range('data_storage', *RATE_POSITIVE),
comment='Data storage rate, like HHD, SSD.')
graphic_card = Column(Float(decimal_return_scale=2), check_range('graphic_card', *RATE_POSITIVE),
comment='Graphic card rate.')
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
class RateComputer(RateMixin, Rate):
"""The act of rating a computer."""
_processor = Column('processor',
Float(decimal_return_scale=Rate.N),
check_range('processor', *R_POSITIVE))
_processor.comment = """The rate of the Processor."""
_ram = Column('ram', Float(decimal_return_scale=Rate.N), check_range('ram', *R_POSITIVE))
_ram.comment = """The rate of the RAM."""
_data_storage = Column('data_storage',
Float(decimal_return_scale=Rate.N),
check_range('data_storage', *R_POSITIVE))
_data_storage.comment = """'Data storage rate, like HHD, SSD.'"""
_graphic_card = Column('graphic_card',
Float(decimal_return_scale=Rate.N),
check_range('graphic_card', *R_POSITIVE))
_graphic_card.comment = 'Graphic card rate.'
@property
def processor(self):
return self._processor
@processor.setter
def processor(self, x):
self._processor = round(x, self.N)
@property
def ram(self):
return self._ram
@ram.setter
def ram(self, x):
self._ram = round(x, self.N)
@property
def data_storage(self):
return self._data_storage
@data_storage.setter
def data_storage(self, x):
self._data_storage = round(x, self.N)
@property
def graphic_card(self):
return self._graphic_card
@graphic_card.setter
def graphic_card(self, x):
self._graphic_card = round(x, self.N)
@property
def data_storage_range(self):
if self.data_storage:
return RatingRange.from_score(self.data_storage)
return RatingRange.from_score(self.data_storage) if self.data_storage else None
@property
def ram_range(self):
if self.ram:
return RatingRange.from_score(self.ram)
return RatingRange.from_score(self.ram) if self.ram else None
@property
def processor_range(self):
if self.processor:
return RatingRange.from_score(self.processor)
return RatingRange.from_score(self.processor) if self.processor else None
@property
def graphic_card_range(self):
if self.graphic_card:
return RatingRange.from_score(self.graphic_card)
return RatingRange.from_score(self.graphic_card) if self.graphic_card else None
@classmethod
def compute(cls, device) -> Tuple['RateComputer', 'Price']:
def compute(cls, device):
"""
The act of compute general computer rate
"""
@ -1004,7 +1055,7 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
@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):
if isinstance(value, (float, int)):
value = Decimal(value)
# equation from marshmallow.fields.Decimal
return value.quantize(Decimal((0, (1,), -cls.SCALE)), rounding=rounding)
@ -1088,7 +1139,7 @@ class EreusePrice(Price):
self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price)
def __init__(self, rating: RateComputer, **kwargs) -> None:
if rating.rating_range == RatingRange.VERY_LOW:
if not rating.rating_range or rating.rating_range == RatingRange.VERY_LOW:
raise InvalidRangeForPrice()
# 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)

View File

@ -2,7 +2,7 @@ import ipaddress
from datetime import datetime, timedelta
from decimal import Decimal
from distutils.version import StrictVersion
from typing import Dict, List, Optional, Set, Union
from typing import Dict, List, Optional, Set, Tuple, Union
from uuid import UUID
from boltons import urlutils
@ -16,9 +16,9 @@ from teal.enums import Country
from ereuse_devicehub.resources.agent.models import Agent
from ereuse_devicehub.resources.device.models import Component, Computer, Device
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ErasureStandards, \
FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RatingSoftware, ReceiverRole, \
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength, BiosAccessRange
from ereuse_devicehub.resources.enums import AppearanceRange, ErasureStandards, \
FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RatingRange, \
ReceiverRole, Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User
@ -42,7 +42,7 @@ class Event(Thing):
severity = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(created, updated)
super().__init__(**kwargs)
self.id = ... # type: UUID
self.name = ... # type: str
self.type = ... # type: str
@ -75,7 +75,6 @@ class Event(Thing):
class EventWithOneDevice(Event):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.device = ... # type: Device
@ -178,8 +177,7 @@ class Install(EventWithOneDevice):
self.address = ... # type: Optional[int]
class SnapshotRequest(Mod assert rate_computer.rating == 4.61
el):
class SnapshotRequest(Model):
def __init__(self, **kwargs) -> None:
self.id = ... # type: UUID
self.request = ... # type: dict
@ -226,6 +224,7 @@ class BenchmarkGraphicCard(BenchmarkWithRate):
class Test(EventWithOneDevice):
elapsed = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.elapsed = ... # type: Optional[timedelta]
@ -233,6 +232,20 @@ class Test(EventWithOneDevice):
class TestDataStorage(Test):
length = ... # type: Column
status = ... # type: Column
lifetime = ... # type: Column
first_error = ... # type: Column
passed_lifetime = ... # type: Column
assessment = ... # type: Column
reallocated_sector_count = ... # type: Column
power_cycle_count = ... # type: Column
reported_uncorrectable_errors = ... # type: Column
command_timeout = ... # type: Column
current_pending_sector_count = ... # type: Column
offline_uncorrectable = ... # type: Column
remaining_lifetime_percentage = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.id = ... # type: UUID
@ -259,70 +272,92 @@ class TestAudio(Test):
"""
Test to check all this aspects related with audio functions, Manual Tests??
"""
loudspeaker = ... # type: Column
microphone = ... # type: Column
_speaker = ... # type: Column
_microphone = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.speaker = ... # type: bool
self.microphone = ... # type: bool
class TestConnectivity(Test):
cellular_network = ... # type: Column
wifi = ... # type: Column
bluetooth = ... # type: Column
usb_port = ... # type: Column
locked = ... # type: Column
class TestBattery(Test):
battery_stat = ... # type: Column
battery_health = ... # type: Column
pass
class TestCamera(Test):
camera = ... # type: Column
pass
class TestKeyboard(Test):
keyboard = ... # type: Column
pass
class TestTrackpad(Test):
trackpad = ... # type: Column
pass
class TestBios(Test):
bios_power_on = ... # type: Column
access_range = ... # type: BiosAccessRange
access_range = ... # type: Column
class TestVisual(ManualRate):
class VisualTest(Test):
appearance_range = ... # type: AppearanceRange
functionality_range = ... # type: FunctionalityRange
labelling = ... # type: Column
class Rate(EventWithOneDevice):
rating = ... # type: Column
appearance = ... # type: Column
functionality = ... # type: Column
N = 2
_rating = ... # type: Column
_appearance = ... # type: Column
_functionality = ... # type: Column
version = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.rating = ... # type: float
self.software = ... # type: RatingSoftware
self.version = ... # type: StrictVersion
self.appearance = ... # type: float
self.functionality = ... # type: float
self.rating_range = ... # type: str
@property
def rating_range(self) -> RatingRange:
pass
class RateComputer(Rate):
id = ...
processor = ...
ram = ...
data_storage = ...
_processor = ... # type: Column
_ram = ... # type: Column
_data_storage = ... # type: Column
_graphic_card = ... # 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
@classmethod
def compute(cls, device):
def compute(cls, device: Device) -> Tuple[RateComputer, EreusePrice]:
pass
@property
def data_storage_range(self) -> Optional[RatingRange]:
pass
@property
def ram_range(self) -> Optional[RatingRange]:
pass
@property
def processor_range(self) -> Optional[RatingRange]:
pass
@property
def graphic_card_range(self) -> Optional[RatingRange]:
pass

View File

@ -1,6 +1,5 @@
from typing import Iterable
import math
from typing import Iterable
from ereuse_devicehub.resources.device.models import Device

View File

@ -1,18 +1,20 @@
from enum import Enum
from itertools import groupby
from typing import Iterable
from typing import Dict, Iterable, Tuple
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Processor, RamModule
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Processor, \
RamModule
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
RateComputer, TestVisual, BenchmarkProcessorSysbench
# todo if no return assign then rate_c = 1 is assigned
# todo fix corner cases, like components characteristics == None
BenchmarkProcessorSysbench, RateComputer, VisualTest
from ereuse_devicehub.resources.event.rate.rate import BaseRate
class RateAlgorithm(BaseRate):
"""
Rate all components in Computer
"""The algorithm that generates the Rate v1.0.
Do not call directly this class, but use
:meth:`ereuse_devicehub.resources.event.models.RateComputer.compute`,
which then calls this.
"""
class Range(Enum):
@ -43,48 +45,43 @@ class RateAlgorithm(BaseRate):
Processor.t: ('processor', ProcessorRate()),
RamModule.t: ('ram', RamRate()),
DataStorage.t: ('data_storage', DataStorageRate())
}
} # type: Dict[str, Tuple[str, BaseRate]]
def compute(self, device: Computer):
def compute(self, device: Computer) -> RateComputer:
"""Generates a new
:class:`ereuse_devicehub.resources.event.models.RateComputer`
for the passed-in device.
"""
Compute RateComputer is a rate (score) ranging from 0 to 4.7
that represents estimating value of use of desktop and laptop computer components.
"""
if not isinstance(device, Computer): # todo can be an assert?
raise CannotRate('Can only rate computers.')
assert isinstance(device, Computer), 'Can only rate computers'
try:
test_visual = device.last_event_of(VisualTest)
except LookupError:
raise CannotRate('You need a visual test.')
rate = RateComputer()
rate.processor = rate.data_storage = rate.ram = 1 # Init
# Group cpus, rams, storages and compute their rate
# Group cpus, rams, storage 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)
field, rate_cls = self.RATES[type]
result = rate_cls.compute(components)
if result:
setattr(rate, field, result)
# TODO is necessary check if TestVisual exists?? cause StopIteration Error
try:
test_visual = next(e for e in device.events if isinstance(e, TestVisual))
except StopIteration:
raise CannotRate('You need a visual test.')
rate_components = self.harmonic_mean_rates(rate.processor, rate.data_storage, rate.ram)
rate.appearance = self.Appearance.from_devicehub(test_visual.appearance_range).value
rate.functionality = self.Functionality.from_devicehub(test_visual.functionality_range).value
rate.functionality = self.Functionality.from_devicehub(
test_visual.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)
rate.rating = rate_components + rate.functionality + rate.appearance
device.events_one.add(rate)
assert 0 <= rate.rating <= 4.7, 'Rate ranges from 0 to 4.7'
return rate
@ -101,18 +98,20 @@ class ProcessorRate(BaseRate):
# 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: RateComputer):
def compute(self, processor: Processor):
""" 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
# todo jn? 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) and not isinstance(e, BenchmarkProcessorSysbench))
# todo fix if benchmark_cpu.rate == 0
# todo jn? fix StopIteration if don't exists BenchmarkProcessor
benchmark_cpu = next(
e for e in reversed(processor.events)
if isinstance(e, BenchmarkProcessor) and not isinstance(e, BenchmarkProcessorSysbench)
)
# todo jn? fix if benchmark_cpu.rate == 0
benchmark_cpu = benchmark_cpu.rate or self.DEFAULT_SCORE
# STEP: Fusion components
@ -129,8 +128,6 @@ class ProcessorRate(BaseRate):
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
@ -146,7 +143,7 @@ class RamRate(BaseRate):
# ram.size.weight; ram.speed.weight;
RAM_WEIGHTS = 0.7, 0.3
def compute(self, ram_devices: Iterable[RamModule], rate: RateComputer):
def compute(self, ram_devices: Iterable[RamModule]):
"""
Obs: RamModule.speed is possible NULL value & size != NULL or NOT??
:return: result is a rate (score) of all RamModule components
@ -203,7 +200,7 @@ class DataStorageRate(BaseRate):
# 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: RateComputer):
def compute(self, data_storage_devices: Iterable[DataStorage]):
"""
Obs: size != NULL and 0 value & read_speed and write_speed != NULL
:return: result is a rate (score) of all DataStorage devices
@ -215,7 +212,7 @@ class DataStorageRate(BaseRate):
# STEP: Filtering, data cleaning and merging of component parts
for storage in data_storage_devices:
# We assume all hdd snapshots have BenchmarkDataStorage
benchmark = next(e for e in storage.events if isinstance(e, BenchmarkDataStorage))
benchmark = storage.last_event_of(BenchmarkDataStorage)
# prevent NULL values
_size = storage.size or 0
size += _size

View File

@ -13,8 +13,8 @@ from ereuse_devicehub.resources import enums
from ereuse_devicehub.resources.agent import schemas as s_agent
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.enums import AppearanceRange, BiosAccessRange, FunctionalityRange, \
PhysicalErasureMethod, PriceSoftware, RATE_POSITIVE, RatingRange, ReceiverRole, \
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength, BatteryHealthRange
PhysicalErasureMethod, R_POSITIVE, RatingRange, ReceiverRole, \
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
from ereuse_devicehub.resources.event import models as m
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing
@ -176,38 +176,24 @@ class StressTest(Test):
class TestAudio(Test):
__doc__ = m.TestAudio.__doc__
loudspeaker = Boolean()
microphone = Boolean()
speaker = Boolean(description=m.TestAudio._speaker.comment)
microphone = Boolean(description=m.TestAudio._microphone.comment)
class TestConnectivity(Test):
__doc__ = m.TestConnectivity.__doc__
cellular_network = Boolean()
wifi = Boolean()
bluetooth = Boolean()
usb_port = Boolean()
locked = Boolean()
class TestBattery(Test):
__doc__ = m.TestBattery.__doc__
battery_stat = Boolean()
battery_health = EnumField(BatteryHealthRange, data_key='batteryHealthRange')
class TestCamera(Test):
__doc__ = m.TestCamera.__doc__
camera = Boolean()
class TestKeyboard(Test):
__doc__ = m.TestKeyboard.__doc__
keyboard = Boolean()
class TestTrackpad(Test):
__doc__ = m.TestTrackpad.__doc__
trackpad = Boolean()
class TestBios(Test):
@ -216,8 +202,8 @@ class TestBios(Test):
access_range = EnumField(BiosAccessRange, data_key='accessRange')
class TestVisual(Test):
__doc__ = m.TestVisual.__doc__
class VisualTest(Test):
__doc__ = m.VisualTest.__doc__
appearance_range = EnumField(AppearanceRange, data_key='appearanceRange')
functionality_range = EnumField(FunctionalityRange, data_key='functionalityRange')
labelling = Boolean()
@ -225,14 +211,21 @@ class TestVisual(Test):
class Rate(EventWithOneDevice):
__doc__ = m.Rate.__doc__
rating = Integer(validate=Range(*RATE_POSITIVE),
rating = Integer(validate=Range(*R_POSITIVE),
dump_only=True,
description=m.Rate.rating.comment)
description=m.Rate._rating.comment)
version = Version(dump_only=True,
description=m.Rate.version.comment)
appearance = Integer(validate=Range(-3, 5), dump_only=True)
functionality = Integer(validate=Range(-3, 5), dump_only=True)
rating_range = EnumField(RatingRange, dump_only=True, data_key='ratingRange')
appearance = Integer(validate=Range(enums.R_NEGATIVE),
dump_only=True,
description=m.Rate._appearance.comment)
functionality = Integer(validate=Range(enums.R_NEGATIVE),
dump_only=True,
description=m.Rate._functionality.comment)
rating_range = EnumField(RatingRange,
dump_only=True,
data_key='ratingRange',
description=m.Rate.rating_range.__doc__)
class RateComputer(Rate):

View File

@ -1,4 +1,3 @@
from contextlib import suppress
from distutils.version import StrictVersion
from typing import List
from uuid import UUID
@ -11,8 +10,7 @@ from teal.resource import View
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import SnapshotSoftware
from ereuse_devicehub.resources.event.models import Event, Snapshot, Rate, RateComputer, InvalidRangeForPrice, \
EreusePrice
from ereuse_devicehub.resources.event.models import Event, RateComputer, Snapshot
from ereuse_devicehub.resources.event.rate.workbench.v1_0 import CannotRate
SUPPORTED_WORKBENCH = StrictVersion('11.0')
@ -94,7 +92,6 @@ class EventView(View):
if price:
snapshot.events.add(price)
db.session.add(snapshot)
db.session().final_flush()
ret = self.schema.jsonify(snapshot) # transform it back

View File

@ -1,109 +1,115 @@
CREATE OR REPLACE FUNCTION add_edge(parent_id uuid, child_id uuid)
/* Adds an edge between ``parent`` and ``child``.
/* Adds an edge between ``parent`` and ``child``.
Designed to work with Directed Acyclic Graphs (DAG)
(or said in another way, trees with multiple parents without cycles).
Designed to work with Directed Acyclic Graphs (DAG)
(or said in another way, trees with multiple parents without cycles).
This method will raise an exception if:
- Parent is the same as child.
- Child contains the parent.
- Edge parent - child already exists.
This method will raise an exception if:
- Parent is the same as child.
- Child contains the parent.
- Edge parent - child already exists.
Influenced by:
- https://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclic-Graphs-DAG
- http://patshaughnessy.net/2017/12/12/installing-the-postgres-ltree-extension
- https://en.wikipedia.org/wiki/Directed_acyclic_graph
*/
RETURNS void AS $$
Influenced by:
- https://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclic-Graphs-DAG
- http://patshaughnessy.net/2017/12/12/installing-the-postgres-ltree-extension
- https://en.wikipedia.org/wiki/Directed_acyclic_graph
*/
RETURNS void AS
$$
DECLARE
parent text := replace(CAST(parent_id as text), '-', '_');
child text := replace(CAST(child_id as text), '-', '_');
parent text := replace(CAST(parent_id as text), '-', '_');
child text := replace(CAST(child_id as text), '-', '_');
BEGIN
if parent = child
then
raise exception 'Cannot create edge: the parent is the same as the child.';
end if;
if parent = child
then
raise exception 'Cannot create edge: the parent is the same as the child.';
end if;
if EXISTS (
SELECT 1 FROM path where path.path ~ CAST('*.' || child || '.*.' || parent || '.*' as lquery)
)
then
raise exception 'Cannot create edge: child already contains parent.';
end if;
if EXISTS(
SELECT 1
FROM path
where path.path ~ CAST('*.' || child || '.*.' || parent || '.*' as lquery)
)
then
raise exception 'Cannot create edge: child already contains parent.';
end if;
-- We have two subgraphs: the parent subgraph that goes from the parent to the root,
-- and the child subgraph, going from the child (which is the root of this subgraph)
-- to all the leafs.
-- We do the cartesian product from all the paths of the parent subgraph that end in the parent
-- WITH all the paths that start from the child that end to its leafs.
insert into path (lot_id, path) (
select distinct lot_id, fp.path || subpath(path.path, index(path.path, text2ltree(child)))
from path, (select path.path from path where path.path ~ CAST('*.' || parent AS lquery)) as fp
where path.path ~ CAST('*.' || child || '.*' AS lquery)
);
-- Cleanup: old paths that start with the child (that where used above in the cartesian product)
-- have became a subset of the result of the cartesian product, thus being redundant.
delete from path where path.path ~ CAST(child || '.*' AS lquery);
-- We have two subgraphs: the parent subgraph that goes from the parent to the root,
-- and the child subgraph, going from the child (which is the root of this subgraph)
-- to all the leafs.
-- We do the cartesian product from all the paths of the parent subgraph that end in the parent
-- WITH all the paths that start from the child that end to its leafs.
insert into path (lot_id, path) (
select distinct lot_id, fp.path || subpath(path.path, index(path.path, text2ltree(child)))
from path,
(select path.path from path where path.path ~ CAST('*.' || parent AS lquery)) as fp
where path.path ~ CAST('*.' || child || '.*' AS lquery)
);
-- Cleanup: old paths that start with the child (that where used above in the cartesian product)
-- have became a subset of the result of the cartesian product, thus being redundant.
delete from path where path.path ~ CAST(child || '.*' AS lquery);
END
$$
LANGUAGE plpgsql;
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_edge(parent_id uuid, child_id uuid)
/* Deletes an edge between ``parent`` and ``child``.
/* Deletes an edge between ``parent`` and ``child``.
Designed to work with DAG (See ``add_edge`` function).
Designed to work with DAG (See ``add_edge`` function).
This method will raise an exception if the relationship does not
exist.
*/
RETURNS void AS $$
This method will raise an exception if the relationship does not
exist.
*/
RETURNS void AS
$$
DECLARE
parent text := replace(CAST(parent_id as text), '-', '_');
child text := replace(CAST(child_id as text), '-', '_');
number int;
parent text := replace(CAST(parent_id as text), '-', '_');
child text := replace(CAST(child_id as text), '-', '_');
number int;
BEGIN
-- to delete we remove from the path of the descendants of the child
-- (and the child) any ancestor coming from this edge.
-- When we added the edge we did a cartesian product. When removing
-- this part of the path we will have duplicate paths.
-- to delete we remove from the path of the descendants of the child
-- (and the child) any ancestor coming from this edge.
-- When we added the edge we did a cartesian product. When removing
-- this part of the path we will have duplicate paths.
-- don't check uniqueness for path key until we delete duplicates
SET CONSTRAINTS path_unique DEFERRED;
-- don't check uniqueness for path key until we delete duplicates
SET CONSTRAINTS path_unique DEFERRED;
-- remove everything above the child lot_id in the path
-- this creates duplicates on path and lot_id
update path
set path = subpath(path, index(path, text2ltree(child)))
where path ~ CAST('*.' || parent || '.' || child || '.*' AS lquery);
-- remove everything above the child lot_id in the path
-- this creates duplicates on path and lot_id
update path
set path = subpath(path, index(path, text2ltree(child)))
where path ~ CAST('*.' || parent || '.' || child || '.*' AS lquery);
-- remove duplicates
-- we need an id field exclusively for this operation
-- from https://wiki.postgresql.org/wiki/Deleting_duplicates
DELETE
FROM path
WHERE id IN (SELECT id
FROM (SELECT id, ROW_NUMBER() OVER (partition BY lot_id, path) AS rnum FROM path) t
WHERE t.rnum > 1);
-- remove duplicates
-- we need an id field exclusively for this operation
-- from https://wiki.postgresql.org/wiki/Deleting_duplicates
DELETE
FROM path
WHERE id IN (SELECT id
FROM (SELECT id, ROW_NUMBER() OVER (partition BY lot_id, path) AS rnum
FROM path) t
WHERE t.rnum > 1);
-- re-activate uniqueness check and perform check
-- todo we should put this in a kind-of finally clause
SET CONSTRAINTS path_unique IMMEDIATE;
-- re-activate uniqueness check and perform check
-- todo we should put this in a kind-of finally clause
SET CONSTRAINTS path_unique IMMEDIATE;
-- After the update the one of the paths of the child will be
-- containing only the child.
-- This can only be when the child has no parent at all.
-- In case the child has more than one parent, remove this path
-- (note that we want it to remove it too from descendants of this
-- child, ex. 'child_id'.'desc1')
select COUNT(1) into number from path where lot_id = child_id;
IF number > 1
THEN
delete from path where path <@ text2ltree(child);
end if;
-- After the update the one of the paths of the child will be
-- containing only the child.
-- This can only be when the child has no parent at all.
-- In case the child has more than one parent, remove this path
-- (note that we want it to remove it too from descendants of this
-- child, ex. 'child_id'.'desc1')
select COUNT(1) into number from path where lot_id = child_id;
IF number > 1
THEN
delete from path where path <@ text2ltree(child);
end if;
END
$$
LANGUAGE plpgsql;
LANGUAGE plpgsql;

View File

@ -1,5 +1,5 @@
from sqlalchemy.util import OrderedSet
from marshmallow.fields import Boolean
from sqlalchemy.util import OrderedSet
from teal.marshmallow import SanitizedStr, URL
from ereuse_devicehub.marshmallow import NestedOn

View File

@ -118,7 +118,7 @@
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
}
],
"manufacturer": "ASUSTeK Computer INC.",

View File

@ -10,7 +10,7 @@ device:
model: d1ml
manufacturer: d1mr
events:
- type: TestVisual
- type: VisualTest
appearanceRange: A
functionalityRange: B
components:

View File

@ -92,7 +92,7 @@
{
"appearanceRange": "D",
"functionalityRange": "D",
"type": "TestVisual"
"type": "VisualTest"
},
{
"elapsed": 300,

View File

@ -137,7 +137,7 @@
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "TestVisual"
"type": "VisualTest"
},
{
"elapsed": 300,

View File

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

View File

@ -1,5 +1,4 @@
import datetime
from datetime import timedelta
from uuid import UUID
import pytest
@ -23,7 +22,7 @@ from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, Mismatch
from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech, Severity, \
SnapshotSoftware
from ereuse_devicehub.resources.event import models as m
from ereuse_devicehub.resources.event.models import Remove, Test
from ereuse_devicehub.resources.event.models import Remove, TestConnectivity
from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.user import User
from tests import conftest
@ -391,14 +390,14 @@ def test_get_device(app: Devicehub, user: UserClient):
])
db.session.add(pc)
# todo test is an abstract class. replace with another one
db.session.add(Test(device=pc,
severity=Severity.Info,
agent=Person(name='Timmy'),
author=User(email='bar@bar.com')))
db.session.add(TestConnectivity(device=pc,
severity=Severity.Info,
agent=Person(name='Timmy'),
author=User(email='bar@bar.com')))
db.session.commit()
pc, _ = user.get(res=d.Device, item=1)
assert len(pc['events']) == 1
assert pc['events'][0]['type'] == 'Test'
assert pc['events'][0]['type'] == 'TestConnectivity'
assert pc['events'][0]['device'] == 1
assert pc['events'][0]['severity'] == 'Info'
assert UUID(pc['events'][0]['author'])

View File

@ -362,7 +362,6 @@ def test_manual_rate_after_workbench_rate(user: UserClient):
s = file('real-hp.snapshot.11')
snapshot, _ = user.post(s, res=models.Snapshot)
device, _ = user.get(res=Device, item=snapshot['device']['id'])
# TODO
assert 'B' == device['rate']['appearanceRange']
assert device['rate'] == 1
user.post({
@ -378,3 +377,27 @@ def test_manual_rate_after_workbench_rate(user: UserClient):
@pytest.mark.xfail(reson='Develop an algorithm that can make rates only from manual rates')
def test_manual_rate_without_workbench_rate(user: UserClient):
pass
@pytest.mark.xfail(reson='develop')
def test_measure_battery():
"""Tests the MeasureBattery."""
# todo jn
@pytest.mark.xfail(reson='develop')
def test_test_camera():
"""Tests the TestCamera."""
# todo jn
@pytest.mark.xfail(reson='develop')
def test_test_keyboard():
"""Tests the TestKeyboard."""
# todo jn
@pytest.mark.xfail(reson='develop')
def test_test_trackpad():
"""Tests the TestTrackpad."""
# todo jn

View File

@ -9,8 +9,8 @@ from ereuse_devicehub.resources.device.models import Computer, Desktop, HardDriv
RamModule
from ereuse_devicehub.resources.enums import AppearanceRange, ComputerChassis, \
FunctionalityRange
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, \
BenchmarkProcessor, RateComputer, TestVisual, Snapshot
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
RateComputer, Snapshot, VisualTest
from ereuse_devicehub.resources.event.rate.workbench.v1_0 import CannotRate
from tests import conftest
from tests.conftest import file
@ -28,16 +28,11 @@ def test_workbench_rate_db():
db.session.commit()
@pytest.mark.xfail(reason='AggreagteRate only takes data from WorkbenchRate as for now')
@pytest.mark.xfail(reason='ComputerRate V1 can only be triggered from Workbench snapshot software')
def test_rate_workbench_then_manual():
"""Checks that a new Rate is generated with a new rate
value when a TestVisual is performed after performing a
RateComputer.
The new Rate needs to be computed by the values of
the appearance and funcitonality grade of TestVisual.
"""Checks that a new Rate is generated for a snapshot
that is not from Workbench.
"""
pass
@pytest.mark.usefixtures(conftest.app_context.__name__)
@ -60,11 +55,9 @@ def test_rate():
}
# Add test visual with functionality and appearance range
visual_test = TestVisual()
visual_test.appearance_range = AppearanceRange.A
visual_test.functionality_range = FunctionalityRange.A
pc.events_one.add(visual_test)
VisualTest(appearance_range=AppearanceRange.A,
functionality_range=FunctionalityRange.A,
device=pc)
rate, price = RateComputer.compute(pc)
# events = pc.events
@ -120,3 +113,14 @@ def test_no_rate_if_device_is_not_computer(user: UserClient):
device = file('keyboard.snapshot')
user.post(device, res=Snapshot)
assert CannotRate
@pytest.mark.xfail(reason='Test not developed')
def test_multiple_rates(user: UserClient):
"""Tests submitting two rates from Workbench,
ensuring that the tests / benchmarks...
from the first rate do not contaminate the second rate.
This ensures that rates only takes the last version of events
and components (in case device has new components, for example).
"""

View File

@ -18,7 +18,7 @@ import pytest
from ereuse_devicehub.resources.device.models import Desktop, HardDrive, Processor, RamModule
from ereuse_devicehub.resources.enums import AppearanceRange, ComputerChassis, FunctionalityRange
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
RateComputer, TestVisual
VisualTest
from ereuse_devicehub.resources.event.rate.workbench.v1_0 import DataStorageRate, ProcessorRate, \
RamRate, RateAlgorithm
@ -32,31 +32,31 @@ def test_rate_data_storage_rate():
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], RateComputer())
data_storage_rate = DataStorageRate().compute([hdd_1969])
assert math.isclose(data_storage_rate, 4.02, rel_tol=0.001), 'DataStorageRate returns incorrect value(rate)'
assert math.isclose(data_storage_rate, 4.02, rel_tol=0.001)
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], RateComputer())
data_storage_rate = DataStorageRate().compute([hdd_3054])
assert math.isclose(data_storage_rate, 4.07, rel_tol=0.001), 'DataStorageRate returns incorrect value(rate)'
assert math.isclose(data_storage_rate, 4.07, rel_tol=0.001)
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], RateComputer())
data_storage_rate = DataStorageRate().compute([hdd_81])
assert math.isclose(data_storage_rate, 2.61, rel_tol=0.001), 'DataStorageRate returns incorrect value(rate)'
assert math.isclose(data_storage_rate, 2.61, rel_tol=0.001)
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], RateComputer())
data_storage_rate = DataStorageRate().compute([hdd_1556])
assert math.isclose(data_storage_rate, 3.70, rel_tol=0.001), 'DataStorageRate returns incorrect value(rate)'
assert math.isclose(data_storage_rate, 3.70, rel_tol=0.001)
def test_rate_data_storage_size_is_null():
@ -69,7 +69,7 @@ def test_rate_data_storage_size_is_null():
hdd_null = HardDrive(size=None)
hdd_null.events_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
data_storage_rate = DataStorageRate().compute([hdd_null], RateComputer())
data_storage_rate = DataStorageRate().compute([hdd_null])
assert data_storage_rate is None
@ -79,7 +79,7 @@ def test_rate_no_data_storage():
"""
hdd_null = HardDrive()
hdd_null.events_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
data_storage_rate = DataStorageRate().compute([hdd_null], RateComputer())
data_storage_rate = DataStorageRate().compute([hdd_null])
assert data_storage_rate is None
@ -94,7 +94,7 @@ def test_rate_ram_rate():
ram1 = RamModule(size=2048, speed=1333)
ram_rate = RamRate().compute([ram1], RateComputer())
ram_rate = RamRate().compute([ram1])
# todo rel_tol >= 0.002
assert math.isclose(ram_rate, 2.02, rel_tol=0.002), 'RamRate returns incorrect value(rate)'
@ -109,7 +109,7 @@ def test_rate_ram_rate_2modules():
ram1 = RamModule(size=4096, speed=1600)
ram2 = RamModule(size=2048, speed=1067)
ram_rate = RamRate().compute([ram1, ram2], RateComputer())
ram_rate = RamRate().compute([ram1, ram2])
assert math.isclose(ram_rate, 3.79, rel_tol=0.001), 'RamRate returns incorrect value(rate)'
@ -125,7 +125,7 @@ def test_rate_ram_rate_4modules():
ram3 = RamModule(size=512, speed=667)
ram4 = RamModule(size=512, speed=533)
ram_rate = RamRate().compute([ram1, ram2, ram3, ram4], RateComputer())
ram_rate = RamRate().compute([ram1, ram2, ram3, ram4])
# todo rel_tol >= 0.002
assert math.isclose(ram_rate, 1.993, rel_tol=0.001), 'RamRate returns incorrect value(rate)'
@ -138,7 +138,7 @@ def test_rate_ram_module_size_is_0():
ram0 = RamModule(size=0, speed=888)
ram_rate = RamRate().compute([ram0], RateComputer())
ram_rate = RamRate().compute([ram0])
assert ram_rate is None
@ -150,13 +150,13 @@ def test_rate_ram_speed_is_null():
ram0 = RamModule(size=2048, speed=None)
ram_rate = RamRate().compute([ram0], RateComputer())
ram_rate = RamRate().compute([ram0])
assert math.isclose(ram_rate, 1.85, rel_tol=0.002), 'RamRate returns incorrect value(rate)'
ram0 = RamModule(size=1024, speed=None)
ram_rate = RamRate().compute([ram0], RateComputer())
ram_rate = RamRate().compute([ram0])
# todo rel_tol >= 0.004
assert math.isclose(ram_rate, 1.25, rel_tol=0.004), 'RamRate returns incorrect value(rate)'
@ -168,7 +168,7 @@ def test_rate_no_ram_module():
"""
ram0 = RamModule()
ram_rate = RamRate().compute([ram0], RateComputer())
ram_rate = RamRate().compute([ram0])
assert ram_rate is None
@ -184,9 +184,9 @@ def test_rate_processor_rate():
# add score processor benchmark
cpu.events_one.add(BenchmarkProcessor(rate=3192.34))
processor_rate = ProcessorRate().compute(cpu, RateComputer())
processor_rate = ProcessorRate().compute(cpu)
assert math.isclose(processor_rate, 1, rel_tol=0.001), 'ProcessorRate returns incorrect value(rate)'
assert math.isclose(processor_rate, 1, rel_tol=0.001)
def test_rate_processor_rate_2cores():
@ -199,17 +199,17 @@ def test_rate_processor_rate_2cores():
# add score processor benchmark
cpu.events_one.add(BenchmarkProcessor(rate=27136.44))
processor_rate = ProcessorRate().compute(cpu, RateComputer())
processor_rate = ProcessorRate().compute(cpu)
assert math.isclose(processor_rate, 3.95, rel_tol=0.001), 'ProcessorRate returns incorrect value(rate)'
assert math.isclose(processor_rate, 3.95, rel_tol=0.001)
cpu = Processor(cores=2, speed=3.3)
cpu.events_one.add(BenchmarkProcessor(rate=26339.48))
processor_rate = ProcessorRate().compute(cpu, RateComputer())
processor_rate = ProcessorRate().compute(cpu)
# todo rel_tol >= 0.002
assert math.isclose(processor_rate, 3.93, rel_tol=0.002), 'ProcessorRate returns incorrect value(rate)'
assert math.isclose(processor_rate, 3.93, rel_tol=0.002)
def test_rate_processor_with_null_cores():
@ -220,10 +220,10 @@ def test_rate_processor_with_null_cores():
# todo try without BenchmarkProcessor, StopIteration problem
cpu.events_one.add(BenchmarkProcessor())
processor_rate = ProcessorRate().compute(cpu, RateComputer())
processor_rate = ProcessorRate().compute(cpu)
# todo rel_tol >= 0.003
assert math.isclose(processor_rate, 1.38, rel_tol=0.003), 'ProcessorRate returns incorrect value(rate)'
assert math.isclose(processor_rate, 1.38, rel_tol=0.003)
def test_rate_processor_with_null_speed():
@ -233,9 +233,9 @@ def test_rate_processor_with_null_speed():
cpu = Processor(cores=1, speed=None)
cpu.events_one.add(BenchmarkProcessor(rate=0))
processor_rate = ProcessorRate().compute(cpu, RateComputer())
processor_rate = ProcessorRate().compute(cpu)
assert math.isclose(processor_rate, 1.06, rel_tol=0.001), 'ProcessorRate returns incorrect value(rate)'
assert math.isclose(processor_rate, 1.06, rel_tol=0.001)
def test_rate_computer_rate():
@ -324,11 +324,9 @@ def test_rate_computer_rate():
cpu
}
# Add test visual with functionality and appearance range
visual_test = TestVisual()
visual_test.appearance_range = AppearanceRange.A
visual_test.functionality_range = FunctionalityRange.A
pc_test.events_one.add(visual_test)
VisualTest(appearance_range=AppearanceRange.A,
functionality_range=FunctionalityRange.A,
device=pc_test)
# Compute all components rates and general rating
rate_pc = RateAlgorithm().compute(pc_test)
@ -353,10 +351,9 @@ def test_rate_computer_rate():
cpu
}
# Add test visual with functionality and appearance range
visual_test = TestVisual()
visual_test.appearance_range = AppearanceRange.B
visual_test.functionality_range = FunctionalityRange.A
pc_test.events_one.add(visual_test)
VisualTest(appearance_range=AppearanceRange.B,
functionality_range=FunctionalityRange.A,
device=pc_test)
# Compute all components rates and general rating
rate_pc = RateAlgorithm().compute(pc_test)
@ -384,12 +381,9 @@ def test_rate_computer_rate():
cpu
}
# Add test visual with functionality and appearance range
visual_test = TestVisual()
visual_test.appearance_range = AppearanceRange.C
visual_test.functionality_range = FunctionalityRange.A
pc_test.events_one.add(visual_test)
VisualTest(appearance_range=AppearanceRange.C,
functionality_range=FunctionalityRange.A,
device=pc_test)
# Compute all components rates and general rating
rate_pc = RateAlgorithm().compute(pc_test)
@ -399,7 +393,7 @@ def test_rate_computer_rate():
assert math.isclose(rate_pc.processor, 1, rel_tol=0.001)
assert math.isclose(rate_pc.rating, 1.58, rel_tol=0.001)
assert rate_pc.rating == 1.57
# Create a new Computer with components characteristics of pc with id = 798
pc_test = Desktop(chassis=ComputerChassis.Tower)
@ -413,11 +407,9 @@ def test_rate_computer_rate():
cpu
}
# Add test visual with functionality and appearance range
visual_test = TestVisual()
visual_test.appearance_range = AppearanceRange.B
visual_test.functionality_range = FunctionalityRange.A
pc_test.events_one.add(visual_test)
VisualTest(appearance_range=AppearanceRange.B,
functionality_range=FunctionalityRange.A,
device=pc_test)
# Compute all components rates and general rating
rate_pc = RateAlgorithm().compute(pc_test)

View File

@ -17,8 +17,8 @@ from ereuse_devicehub.resources.device.models import SolidStateDrive
from ereuse_devicehub.resources.device.sync import MismatchBetweenProperties, \
MismatchBetweenTagsAndHid
from ereuse_devicehub.resources.enums import ComputerChassis, SnapshotSoftware
from ereuse_devicehub.resources.event.models import BenchmarkProcessor, \
EraseSectors, Event, Snapshot, SnapshotRequest, RateComputer, TestVisual, BenchmarkDataStorage
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, BenchmarkProcessor, \
EraseSectors, Event, RateComputer, Snapshot, SnapshotRequest, VisualTest
from ereuse_devicehub.resources.tag import Tag
from ereuse_devicehub.resources.user.models import User
from tests.conftest import file
@ -70,7 +70,7 @@ def test_snapshot_post(user: UserClient):
snapshot = snapshot_and_check(user, file('basic.snapshot'),
event_types=(
BenchmarkProcessor.t,
TestVisual.t,
VisualTest.t,
RateComputer.t
),
perform_second_snapshot=False)
@ -116,15 +116,17 @@ def test_snapshot_component_add_remove(user: UserClient):
# (represented with their S/N) should be:
# PC 1: p1c1s, p1c2s, p1c3s. PC 2: ø
s1 = file('1-device-with-components.snapshot')
# TODO if dont put len([event_types]) = 1 != len(event_types) = 18 is BenchmarkProcessor (str 18 chars); why??
snapshot1 = snapshot_and_check(user, s1, event_types=('BenchmarkProcessor',), perform_second_snapshot=False)
snapshot1 = snapshot_and_check(user,
s1,
event_types=(BenchmarkProcessor.t,),
perform_second_snapshot=False)
pc1_id = snapshot1['device']['id']
pc1, _ = user.get(res=m.Device, item=pc1_id)
# Parent contains components
assert tuple(c['serialNumber'] for c in pc1['components']) == ('p1c1s', 'p1c2s', 'p1c3s')
# Components contain parent
assert all(c['parent'] == pc1_id for c in pc1['components'])
# pc has Snapshot as event
# pc has two events: Snapshot and the BenchmarkProcessor
# TODO change assert to len(pc1['events']) == 2 cause we add BenchmarkProcessor event
assert len(pc1['events']) == 2
# TODO pc1['events'][0]['type'] == BenchmarkProcessor.t
@ -154,7 +156,8 @@ def test_snapshot_component_add_remove(user: UserClient):
assert tuple(e['type'] for e in pc2['events']) == ('Snapshot',)
# p1c2s has two Snapshots, a Remove and an Add
p1c2s, _ = user.get(res=m.Device, item=pc2['components'][0]['id'])
assert tuple(e['type'] for e in p1c2s['events']) == ('BenchmarkProcessor', 'Snapshot', 'Snapshot', 'Remove')
assert tuple(e['type'] for e in p1c2s['events']) == (
'BenchmarkProcessor', 'Snapshot', 'Snapshot', 'Remove')
# We register the first device again, but removing motherboard
# and moving processor from the second device to the first.
@ -243,11 +246,7 @@ def test_snapshot_tag_inner_tag(tag_id: str, user: UserClient, app: Devicehub):
b['device']['tags'] = [{'type': 'Tag', 'id': tag_id}]
snapshot_and_check(user, b,
event_types=(
RateComputer.t,
BenchmarkProcessor.t,
TestVisual.t
), perform_second_snapshot=False)
event_types=(RateComputer.t, BenchmarkProcessor.t, VisualTest.t))
with app.app_context():
tag = Tag.query.one() # type: Tag
assert tag.device_id == 1, 'Tag should be linked to the first device'
@ -315,15 +314,17 @@ def test_erase_privacy_standards(user: UserClient):
EraseSectors.t,
BenchmarkDataStorage.t,
BenchmarkProcessor.t
), perform_second_snapshot=True)
))
erase = next(e for e in snapshot['events'] if e['type'] == EraseSectors.t)
assert '2018-06-01T07:12:06+00:00' == erase['endTime']
storage = next(e for e in snapshot['components'] if e['type'] == SolidStateDrive.t)
storage, _ = user.get(res=m.Device, item=storage['id']) # Let's get storage events too
# order: creation time ascending
erasure1, benchmark_data_storage1, _snapshot1, erasure2, benchmark_data_storage2, _snapshot2 = storage['events']
erasure1, benchmark_data_storage1, _snapshot1, erasure2, benchmark_data_storage2, _snapshot2 = \
storage['events']
assert erasure1['type'] == erasure2['type'] == 'EraseSectors'
assert benchmark_data_storage1['type'] == benchmark_data_storage2['type'] == 'BenchmarkDataStorage'
assert benchmark_data_storage1['type'] == benchmark_data_storage2[
'type'] == 'BenchmarkDataStorage'
assert _snapshot1['type'] == _snapshot2['type'] == 'Snapshot'
get_snapshot, _ = user.get(res=Event, item=_snapshot2['id'])
assert get_snapshot['events'][0]['endTime'] == '2018-06-01T07:12:06+00:00'
@ -381,7 +382,7 @@ def test_snapshot_computer_monitor(user: UserClient):
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient):
s = file('smartphone.snapshot')
snapshot = snapshot_and_check(user, s, event_types=('TestVisual',))
snapshot = snapshot_and_check(user, s, event_types=('VisualTest',))
mobile, _ = user.get(res=m.Device, item=snapshot['device']['id'])
assert mobile['imei'] == 3568680000414120
# todo check that manual rate has been created
@ -429,7 +430,7 @@ def assert_similar_components(components1: List[dict], components2: List[dict]):
def snapshot_and_check(user: UserClient,
input_snapshot: dict,
event_types: Tuple[str] = tuple(),
event_types: Tuple[str, ...] = tuple(),
perform_second_snapshot=True) -> dict:
"""
Performs a Snapshot and then checks if the result is ok:

View File

@ -36,7 +36,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} == {
('RateComputer', 1), # Only (RateComputer, 1), delete (Rate, 1)
('RateComputer', 1),
('BenchmarkProcessorSysbench', 5),
('StressTest', 1),
('EraseSectors', 6),
@ -47,7 +47,7 @@ def test_workbench_server_condensed(user: UserClient):
('BenchmarkDataStorage', 6),
('BenchmarkDataStorage', 7),
('TestDataStorage', 6),
('TestVisual', 1)
('VisualTest', 1)
}
assert snapshot['closed']
assert snapshot['severity'] == 'Info'
@ -62,11 +62,9 @@ def test_workbench_server_condensed(user: UserClient):
assert device['rate']['closed']
assert device['rate']['severity'] == 'Info'
assert device['rate']['rating'] == 0
assert device['rate']['type'] == 'RateComputer' # New in rate v2
# new asserts get in TestVisual event info; why change in every execution device['events'][X]
# assert device['events'][0]['appearanceRange'] == 'A'
# assert device['events'][0]['functionalityRange'] == 'B'
# TODO add appearance and functionality Range in device[rate]
assert device['rate']['type'] == 'RateComputer'
assert device['events'][0]['appearanceRange'] == 'A'
assert device['events'][0]['functionalityRange'] == 'B'
assert device['tags'][0]['id'] == 'tag1'
@ -154,8 +152,8 @@ def test_real_hp_11(user: UserClient):
'TestDataStorage',
'BenchmarkRamSysbench',
'StressTest',
'TestBios', # New in rate v2
'TestVisual' # New in rate v2
'TestBios',
'VisualTest'
}
assert len(list(e['type'] for e in snapshot['events'])) == 10
assert pc['networkSpeeds'] == [1000, None], 'Device has no WiFi'
@ -188,7 +186,6 @@ def test_snapshot_real_eee_1001pxd(user: UserClient):
assert pc['networkSpeeds'] == [100, 0], 'Although it has WiFi we do not know the speed'
assert pc['rate']
rate = pc['rate']
# new asserts get in TestVisual event info; why change pc['events'][X]
# assert pc['events'][0]['appearanceRange'] == 'A'
# assert pc['events'][0]['functionalityRange'] == 'B'
# TODO add appearance and functionality Range in device[rate]