refactor tests to new rate version
This commit is contained in:
parent
880b5e706e
commit
5ec7ed54e6
|
@ -42,4 +42,4 @@ def test_api_docs(client: Client):
|
||||||
'scheme': 'basic',
|
'scheme': 'basic',
|
||||||
'name': 'Authorization'
|
'name': 'Authorization'
|
||||||
}
|
}
|
||||||
assert len(docs['definitions']) == 96
|
assert len(docs['definitions']) == 102
|
||||||
|
|
|
@ -389,8 +389,8 @@ def test_get_device(app: Devicehub, user: UserClient):
|
||||||
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
|
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
|
||||||
])
|
])
|
||||||
db.session.add(pc)
|
db.session.add(pc)
|
||||||
|
# todo test is an abstract class. replace with another one
|
||||||
db.session.add(Test(device=pc,
|
db.session.add(Test(device=pc,
|
||||||
elapsed=timedelta(seconds=4),
|
|
||||||
severity=Severity.Info,
|
severity=Severity.Info,
|
||||||
agent=Person(name='Timmy'),
|
agent=Person(name='Timmy'),
|
||||||
author=User(email='bar@bar.com')))
|
author=User(email='bar@bar.com')))
|
||||||
|
@ -399,7 +399,6 @@ def test_get_device(app: Devicehub, user: UserClient):
|
||||||
assert len(pc['events']) == 1
|
assert len(pc['events']) == 1
|
||||||
assert pc['events'][0]['type'] == 'Test'
|
assert pc['events'][0]['type'] == 'Test'
|
||||||
assert pc['events'][0]['device'] == 1
|
assert pc['events'][0]['device'] == 1
|
||||||
assert pc['events'][0]['elapsed'] == 4
|
|
||||||
assert pc['events'][0]['severity'] == 'Info'
|
assert pc['events'][0]['severity'] == 'Info'
|
||||||
assert UUID(pc['events'][0]['author'])
|
assert UUID(pc['events'][0]['author'])
|
||||||
assert 'events_components' not in pc, 'events_components are internal use only'
|
assert 'events_components' not in pc, 'events_components are internal use only'
|
||||||
|
|
|
@ -6,51 +6,45 @@ import pytest
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.device.models import Computer, Desktop, HardDrive, Processor, \
|
from ereuse_devicehub.resources.device.models import Computer, Desktop, HardDrive, Processor, \
|
||||||
RamModule
|
RamModule
|
||||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ComputerChassis, \
|
from ereuse_devicehub.resources.enums import AppearanceRange, ComputerChassis, \
|
||||||
FunctionalityRange, RatingSoftware
|
FunctionalityRange
|
||||||
from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkDataStorage, \
|
from ereuse_devicehub.resources.event.models import BenchmarkDataStorage, \
|
||||||
BenchmarkProcessor, EreusePrice, WorkbenchRate
|
BenchmarkProcessor, EreusePrice, RateComputer, TestVisual
|
||||||
from ereuse_devicehub.resources.event.rate import main
|
|
||||||
from tests import conftest
|
from tests import conftest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||||
def test_workbench_rate_db():
|
def test_workbench_rate_db():
|
||||||
rate = WorkbenchRate(processor=0.1,
|
rate = RateComputer(processor=0.1,
|
||||||
ram=1.0,
|
ram=1.0,
|
||||||
bios_range=Bios.A,
|
labelling=False,
|
||||||
labelling=False,
|
graphic_card=0.1,
|
||||||
graphic_card=0.1,
|
data_storage=4.1,
|
||||||
data_storage=4.1,
|
version=StrictVersion('1.0'),
|
||||||
software=RatingSoftware.ECost,
|
device=Computer(serial_number='24', chassis=ComputerChassis.Tower))
|
||||||
version=StrictVersion('1.0'),
|
|
||||||
device=Computer(serial_number='24', chassis=ComputerChassis.Tower))
|
|
||||||
db.session.add(rate)
|
db.session.add(rate)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='AggreagteRate only takes data from WorkbenchRate as for now')
|
@pytest.mark.xfail(reason='AggreagteRate only takes data from WorkbenchRate as for now')
|
||||||
def test_rate_workbench_then_manual():
|
def test_rate_workbench_then_manual():
|
||||||
"""Checks that a new AggregateRate is generated with a new rate
|
"""Checks that a new Rate is generated with a new rate
|
||||||
value when a ManualRate is performed after performing a
|
value when a TestVisual is performed after performing a
|
||||||
WorkbenchRate.
|
RateComputer.
|
||||||
|
|
||||||
The new AggregateRate needs to be computed by the values of
|
The new Rate needs to be computed by the values of
|
||||||
the WorkbenchRate + new values from ManualRate.
|
the appearance and funcitonality grade of TestVisual.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
def test_rate():
|
def test_rate():
|
||||||
"""Test generating an AggregateRate for a given PC / components /
|
"""Test generating an Rate for a given PC / components /
|
||||||
WorkbenchRate ensuring results and relationships between
|
RateComputer ensuring results and relationships between
|
||||||
pc - rate - workbenchRate - price.
|
pc - rate - RateComputer - price.
|
||||||
"""
|
"""
|
||||||
rate = WorkbenchRate(
|
rate = RateComputer()
|
||||||
appearance_range=AppearanceRange.A,
|
|
||||||
functionality_range=FunctionalityRange.A
|
|
||||||
)
|
|
||||||
pc = Desktop(chassis=ComputerChassis.Tower)
|
pc = Desktop(chassis=ComputerChassis.Tower)
|
||||||
hdd = HardDrive(size=476940)
|
hdd = HardDrive(size=476940)
|
||||||
hdd.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
|
hdd.events_one.add(BenchmarkDataStorage(read_speed=126, write_speed=29.8))
|
||||||
|
@ -62,8 +56,15 @@ def test_rate():
|
||||||
RamModule(size=2048, speed=1067),
|
RamModule(size=2048, speed=1067),
|
||||||
cpu
|
cpu
|
||||||
}
|
}
|
||||||
rate.device = pc
|
|
||||||
events = main.main(rate, RatingSoftware.ECost, StrictVersion('1.0'))
|
# 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)
|
||||||
|
# TODO why events_one?? how to rewrite correctly this tests??
|
||||||
|
events = rate.compute(pc)
|
||||||
price = next(e for e in events if isinstance(e, EreusePrice))
|
price = next(e for e in events if isinstance(e, EreusePrice))
|
||||||
assert price.price == Decimal('92.2001')
|
assert price.price == Decimal('92.2001')
|
||||||
assert price.retailer.standard.amount == Decimal('40.9714')
|
assert price.retailer.standard.amount == Decimal('40.9714')
|
||||||
|
@ -76,16 +77,7 @@ def test_rate():
|
||||||
assert price.platform.warranty2.amount == Decimal('25.4357')
|
assert price.platform.warranty2.amount == Decimal('25.4357')
|
||||||
assert price.refurbisher.warranty2.amount == Decimal('43.7259')
|
assert price.refurbisher.warranty2.amount == Decimal('43.7259')
|
||||||
assert price.warranty2 == Decimal('124.47')
|
assert price.warranty2 == Decimal('124.47')
|
||||||
|
# TODO How to check new relationships??
|
||||||
# Checks relationships
|
# Checks relationships
|
||||||
workbench_rate = next(e for e in events if isinstance(e, WorkbenchRate))
|
rate_computer = next(e for e in events if isinstance(e, RateComputer))
|
||||||
aggregate_rate = next(e for e in events if isinstance(e, AggregateRate))
|
assert rate_computer.rating == 4.61
|
||||||
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
|
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
import math
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device.models import HardDrive, Processor, RamModule, Device
|
|
||||||
from ereuse_devicehub.resources.event.rate.workbench.v2_0 import Rate
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Evaluate')
|
|
||||||
def test_ratev2_general():
|
|
||||||
"""
|
|
||||||
Test to check if compute all aspects (quality, functionality and appearance) correctly
|
|
||||||
|
|
||||||
Quality rate aspects:
|
|
||||||
Display (screen)
|
|
||||||
Processor
|
|
||||||
RAM
|
|
||||||
Data Storage
|
|
||||||
Battery
|
|
||||||
Camera
|
|
||||||
|
|
||||||
Functionality rate aspects on mobile devices
|
|
||||||
SIM
|
|
||||||
USB/ Charger plug
|
|
||||||
Wi-Fi
|
|
||||||
Bluetooth
|
|
||||||
Fingerprint sensor
|
|
||||||
Loudspeaker
|
|
||||||
Microphone
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
device_test = Device()
|
|
||||||
device_test.components |= {
|
|
||||||
Processor(cores=2, speed=3.4), # CPU
|
|
||||||
HardDrive(size=476940), # HDD
|
|
||||||
RamModule(size=4096, speed=1600), # RAM
|
|
||||||
RamModule(size=2048, speed=1067), # RAM
|
|
||||||
Display(size=5.5, resolutionH=1080, resolutionW=1920), # Screen
|
|
||||||
Battery(capacity=3000), # Mobile devices
|
|
||||||
Camera(resolution=16)
|
|
||||||
}
|
|
||||||
|
|
||||||
rate_device = Rate().compute(device_test)
|
|
||||||
|
|
||||||
assert math.isclose(rate_device, 2.2, rel_tol=0.001)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_general_rate_without_quality():
|
|
||||||
"""
|
|
||||||
Test to check if compute correctly general rate if quality rate are missing..
|
|
||||||
Final Rate = Func Rate + App Rate
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_general_rate_without_functionality():
|
|
||||||
"""
|
|
||||||
Test to check if compute correctly general rate if functionality rate are missing..
|
|
||||||
Final Rate = Quality Rate + App Rate
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_general_rate_without_appearance():
|
|
||||||
"""
|
|
||||||
Test to check if compute correctly general rate if appearance rate are missing..
|
|
||||||
Final Rate = Quality Rate + Functionality Rate
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_general_rate_without_quality():
|
|
||||||
"""
|
|
||||||
Test to check if compute correctly general rate if quality rate are missing..
|
|
||||||
Final Rate = Func Rate + App Rate
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# QUALITY RATE TEST CODE
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_quality_rate():
|
|
||||||
"""
|
|
||||||
Quality Rate Test
|
|
||||||
Test to check all quality aspects, we suppose that we have full snapshot with all information and benchmarks
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_component_rate_equal_to_zero():
|
|
||||||
"""
|
|
||||||
Quality Rate Test
|
|
||||||
Test to check quality aspects with some fields equal to 0 or null
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# FUNCTIONALITY RATE TEST DONE
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_functionality_rate():
|
|
||||||
"""
|
|
||||||
Functionality Rate Test
|
|
||||||
Tests to check all aspects of functionality, we assume we have a complete snapshot with all the information and tests performed.a
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Develop')
|
|
||||||
def test_functionality_rate_miss_tests():
|
|
||||||
"""
|
|
||||||
Functionality Rate Test
|
|
||||||
Test to check if functionality rate compute correctly with some test without any information.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Discuss')
|
|
||||||
def test_appearance_rate():
|
|
||||||
"""
|
|
||||||
Test to check if compute correctly a new rate of a device, only with visual test
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Discuss')
|
|
||||||
def test_update_rate_with_manual_rate():
|
|
||||||
"""
|
|
||||||
Test to check if compute correctly a new rate of a device, if this device input after a manual rate (like visual test)
|
|
||||||
Computing a new rate with old snapshot information score and aggregate a new test information score.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ from ereuse_devicehub.resources.device.exceptions import NeedsId
|
||||||
from ereuse_devicehub.resources.device.sync import MismatchBetweenProperties, \
|
from ereuse_devicehub.resources.device.sync import MismatchBetweenProperties, \
|
||||||
MismatchBetweenTagsAndHid
|
MismatchBetweenTagsAndHid
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis, SnapshotSoftware
|
from ereuse_devicehub.resources.enums import ComputerChassis, SnapshotSoftware
|
||||||
from ereuse_devicehub.resources.event.models import AggregateRate, BenchmarkProcessor, \
|
from ereuse_devicehub.resources.event.models import BenchmarkProcessor, \
|
||||||
EraseSectors, Event, Snapshot, SnapshotRequest, WorkbenchRate
|
EraseSectors, Event, Snapshot, SnapshotRequest, RateComputer, Rate
|
||||||
from ereuse_devicehub.resources.tag import Tag
|
from ereuse_devicehub.resources.tag import Tag
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
from tests.conftest import file
|
from tests.conftest import file
|
||||||
|
@ -65,10 +65,10 @@ def test_snapshot_post(user: UserClient):
|
||||||
Tests the post snapshot endpoint (validation, etc), data correctness,
|
Tests the post snapshot endpoint (validation, etc), data correctness,
|
||||||
and relationship correctness.
|
and relationship correctness.
|
||||||
"""
|
"""
|
||||||
|
# TODO add all event_types to check, how to add correctly??
|
||||||
snapshot = snapshot_and_check(user, file('basic.snapshot'),
|
snapshot = snapshot_and_check(user, file('basic.snapshot'),
|
||||||
event_types=(
|
event_types=(
|
||||||
WorkbenchRate.t,
|
RateComputer.t,
|
||||||
AggregateRate.t,
|
|
||||||
BenchmarkProcessor.t
|
BenchmarkProcessor.t
|
||||||
),
|
),
|
||||||
perform_second_snapshot=False)
|
perform_second_snapshot=False)
|
||||||
|
@ -87,7 +87,7 @@ def test_snapshot_post(user: UserClient):
|
||||||
|
|
||||||
assert {c['type'] for c in snapshot['components']} == {m.GraphicCard.t, m.RamModule.t,
|
assert {c['type'] for c in snapshot['components']} == {m.GraphicCard.t, m.RamModule.t,
|
||||||
m.Processor.t}
|
m.Processor.t}
|
||||||
rate = next(e for e in snapshot['events'] if e['type'] == WorkbenchRate.t)
|
rate = next(e for e in snapshot['events'] if e['type'] == RateComputer.t)
|
||||||
rate, _ = user.get(res=Event, item=rate['id'])
|
rate, _ = user.get(res=Event, item=rate['id'])
|
||||||
assert rate['device']['id'] == snapshot['device']['id']
|
assert rate['device']['id'] == snapshot['device']['id']
|
||||||
rate['components'].sort(key=key)
|
rate['components'].sort(key=key)
|
||||||
|
@ -234,8 +234,10 @@ def test_snapshot_tag_inner_tag(tag_id: str, user: UserClient, app: Devicehub):
|
||||||
"""Tests a posting Snapshot with a local tag."""
|
"""Tests a posting Snapshot with a local tag."""
|
||||||
b = file('basic.snapshot')
|
b = file('basic.snapshot')
|
||||||
b['device']['tags'] = [{'type': 'Tag', 'id': tag_id}]
|
b['device']['tags'] = [{'type': 'Tag', 'id': tag_id}]
|
||||||
|
|
||||||
|
# TODO add all event_types to check, how to add correctly??
|
||||||
snapshot_and_check(user, b,
|
snapshot_and_check(user, b,
|
||||||
event_types=(WorkbenchRate.t, AggregateRate.t, BenchmarkProcessor.t))
|
event_types=(RateComputer.t, BenchmarkProcessor.t))
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
tag = Tag.query.one() # type: Tag
|
tag = Tag.query.one() # type: Tag
|
||||||
assert tag.device_id == 1, 'Tag should be linked to the first device'
|
assert tag.device_id == 1, 'Tag should be linked to the first device'
|
||||||
|
@ -352,6 +354,8 @@ def test_test_data_storage(user: UserClient):
|
||||||
assert incidence_test['severity'] == 'Error'
|
assert incidence_test['severity'] == 'Error'
|
||||||
|
|
||||||
|
|
||||||
|
# TODO change to RateMonitor
|
||||||
|
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
|
||||||
def test_snapshot_computer_monitor(user: UserClient):
|
def test_snapshot_computer_monitor(user: UserClient):
|
||||||
s = file('computer-monitor.snapshot')
|
s = file('computer-monitor.snapshot')
|
||||||
snapshot_and_check(user, s, event_types=('ManualRate',))
|
snapshot_and_check(user, s, event_types=('ManualRate',))
|
||||||
|
@ -448,6 +452,8 @@ def snapshot_and_check(user: UserClient,
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
|
# TODO change to which Rate??
|
||||||
|
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
|
||||||
def test_snapshot_keyboard(user: UserClient):
|
def test_snapshot_keyboard(user: UserClient):
|
||||||
s = file('keyboard.snapshot')
|
s = file('keyboard.snapshot')
|
||||||
snapshot = snapshot_and_check(user, s, event_types=('ManualRate',))
|
snapshot = snapshot_and_check(user, s, event_types=('ManualRate',))
|
||||||
|
|
|
@ -36,8 +36,8 @@ def test_workbench_server_condensed(user: UserClient):
|
||||||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||||
events = snapshot['events']
|
events = snapshot['events']
|
||||||
assert {(event['type'], event['device']) for event in events} == {
|
assert {(event['type'], event['device']) for event in events} == {
|
||||||
('AggregateRate', 1),
|
('Rate', 1),
|
||||||
('WorkbenchRate', 1),
|
('RateComputer', 1),
|
||||||
('BenchmarkProcessorSysbench', 5),
|
('BenchmarkProcessorSysbench', 5),
|
||||||
('StressTest', 1),
|
('StressTest', 1),
|
||||||
('EraseSectors', 6),
|
('EraseSectors', 6),
|
||||||
|
@ -109,7 +109,7 @@ def test_workbench_server_phases(user: UserClient):
|
||||||
assert events[0]['type'] == 'Rate'
|
assert events[0]['type'] == 'Rate'
|
||||||
assert events[0]['device'] == 1
|
assert events[0]['device'] == 1
|
||||||
assert events[0]['closed']
|
assert events[0]['closed']
|
||||||
assert events[0]['type'] == 'WorkbenchRate'
|
assert events[0]['type'] == 'RateComputer'
|
||||||
assert events[0]['device'] == 1
|
assert events[0]['device'] == 1
|
||||||
assert events[1]['type'] == 'BenchmarkProcessor'
|
assert events[1]['type'] == 'BenchmarkProcessor'
|
||||||
assert events[1]['device'] == 5
|
assert events[1]['device'] == 5
|
||||||
|
@ -144,8 +144,8 @@ def test_real_hp_11(user: UserClient):
|
||||||
assert pc['chassis'] == 'Tower'
|
assert pc['chassis'] == 'Tower'
|
||||||
assert set(e['type'] for e in snapshot['events']) == {
|
assert set(e['type'] for e in snapshot['events']) == {
|
||||||
'EreusePrice',
|
'EreusePrice',
|
||||||
'AggregateRate',
|
'Rate',
|
||||||
'WorkbenchRate',
|
'RateComputer',
|
||||||
'BenchmarkDataStorage',
|
'BenchmarkDataStorage',
|
||||||
'BenchmarkProcessor',
|
'BenchmarkProcessor',
|
||||||
'BenchmarkProcessorSysbench',
|
'BenchmarkProcessorSysbench',
|
||||||
|
@ -191,7 +191,7 @@ def test_snapshot_real_eee_1001pxd(user: UserClient):
|
||||||
assert rate['ratingRange'] == 'VERY_LOW'
|
assert rate['ratingRange'] == 'VERY_LOW'
|
||||||
assert rate['ram'] == 1.53
|
assert rate['ram'] == 1.53
|
||||||
assert rate['data_storage'] == 3.76
|
assert rate['data_storage'] == 3.76
|
||||||
assert rate['type'] == 'AggregateRate'
|
assert rate['type'] == 'Rate'
|
||||||
assert rate['biosRange'] == 'C'
|
assert rate['biosRange'] == 'C'
|
||||||
assert rate['appearance'] == 0, 'appearance B equals 0 points'
|
assert rate['appearance'] == 0, 'appearance B equals 0 points'
|
||||||
# todo fix gets correctly functionality rates values not equals to 0.
|
# todo fix gets correctly functionality rates values not equals to 0.
|
||||||
|
|
Reference in a new issue