Merge pull request #170 from eReuse/feature/delete-devices
Feature/delete devices
This commit is contained in:
commit
b117a4b267
|
@ -12,6 +12,7 @@ ml).
|
||||||
[1.0.10-beta]
|
[1.0.10-beta]
|
||||||
|
|
||||||
## [1.0.10-beta]
|
## [1.0.10-beta]
|
||||||
|
- [addend] #170 can delete/deactivate devices.
|
||||||
- [bugfix] #168 can to do a trade without devices.
|
- [bugfix] #168 can to do a trade without devices.
|
||||||
- [added] #167 new actions of status devices: use, recycling, refurbish and management.
|
- [added] #167 new actions of status devices: use, recycling, refurbish and management.
|
||||||
- [changes] #177 new structure of trade.
|
- [changes] #177 new structure of trade.
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""adding active in device
|
||||||
|
|
||||||
|
Revision ID: 8571fb32c912
|
||||||
|
Revises: 968b79fa7756
|
||||||
|
Create Date: 2021-10-05 12:27:09.685227
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op, context
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '8571fb32c912'
|
||||||
|
down_revision = '968b79fa7756'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_inv():
|
||||||
|
INV = context.get_x_argument(as_dictionary=True).get('inventory')
|
||||||
|
if not INV:
|
||||||
|
raise ValueError("Inventory value is not specified")
|
||||||
|
return INV
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_data():
|
||||||
|
con = op.get_bind()
|
||||||
|
sql = f"update {get_inv()}.device set active='t';"
|
||||||
|
con.execute(sql)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('device', sa.Column('active', sa.Boolean(),
|
||||||
|
default=True,
|
||||||
|
nullable=True),
|
||||||
|
schema=f'{get_inv()}')
|
||||||
|
|
||||||
|
upgrade_data()
|
||||||
|
op.alter_column('device', 'active', nullable=False, schema=f'{get_inv()}')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_column('device', 'active', schema=f'{get_inv()}')
|
|
@ -275,6 +275,11 @@ class MakeAvailable(ActionDef):
|
||||||
SCHEMA = schemas.MakeAvailable
|
SCHEMA = schemas.MakeAvailable
|
||||||
|
|
||||||
|
|
||||||
|
class Delete(ActionDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Delete
|
||||||
|
|
||||||
|
|
||||||
class ConfirmDef(ActionDef):
|
class ConfirmDef(ActionDef):
|
||||||
VIEW = None
|
VIEW = None
|
||||||
SCHEMA = schemas.Confirm
|
SCHEMA = schemas.Confirm
|
||||||
|
|
|
@ -1773,6 +1773,14 @@ class MoveOnDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments):
|
||||||
container_to_id.comment = """This is the trade document used as container in a outgoing lot"""
|
container_to_id.comment = """This is the trade document used as container in a outgoing lot"""
|
||||||
|
|
||||||
|
|
||||||
|
class Delete(ActionWithMultipleDevices):
|
||||||
|
# TODO in a new architecture we need rename this class to Deactivate
|
||||||
|
|
||||||
|
"""The act save in device who and why this devices was delete.
|
||||||
|
We never delete one device, but we can deactivate."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Migrate(JoinedTableMixin, ActionWithMultipleDevices):
|
class Migrate(JoinedTableMixin, ActionWithMultipleDevices):
|
||||||
"""Moves the devices to a new database/inventory. Devices cannot be
|
"""Moves the devices to a new database/inventory. Devices cannot be
|
||||||
modified anymore at the previous database.
|
modified anymore at the previous database.
|
||||||
|
|
|
@ -79,6 +79,15 @@ class ActionWithMultipleDevices(Action):
|
||||||
collection_class=OrderedSet)
|
collection_class=OrderedSet)
|
||||||
|
|
||||||
|
|
||||||
|
class ActionWithMultipleDevicesCheckingOwner(ActionWithMultipleDevices):
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def check_owner_of_device(self, data):
|
||||||
|
for dev in data['devices']:
|
||||||
|
if dev.owner != g.user:
|
||||||
|
raise ValidationError("Some Devices not exist")
|
||||||
|
|
||||||
|
|
||||||
class Add(ActionWithOneDevice):
|
class Add(ActionWithOneDevice):
|
||||||
__doc__ = m.Add.__doc__
|
__doc__ = m.Add.__doc__
|
||||||
|
|
||||||
|
@ -87,7 +96,7 @@ class Remove(ActionWithOneDevice):
|
||||||
__doc__ = m.Remove.__doc__
|
__doc__ = m.Remove.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Allocate(ActionWithMultipleDevices):
|
class Allocate(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.Allocate.__doc__
|
__doc__ = m.Allocate.__doc__
|
||||||
start_time = DateTime(data_key='startTime', required=True,
|
start_time = DateTime(data_key='startTime', required=True,
|
||||||
description=m.Action.start_time.comment)
|
description=m.Action.start_time.comment)
|
||||||
|
@ -121,7 +130,7 @@ class Allocate(ActionWithMultipleDevices):
|
||||||
device.allocated = True
|
device.allocated = True
|
||||||
|
|
||||||
|
|
||||||
class Deallocate(ActionWithMultipleDevices):
|
class Deallocate(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.Deallocate.__doc__
|
__doc__ = m.Deallocate.__doc__
|
||||||
start_time = DateTime(data_key='startTime', required=True,
|
start_time = DateTime(data_key='startTime', required=True,
|
||||||
description=m.Action.start_time.comment)
|
description=m.Action.start_time.comment)
|
||||||
|
@ -412,15 +421,15 @@ class Snapshot(ActionWithOneDevice):
|
||||||
field_names=['elapsed'])
|
field_names=['elapsed'])
|
||||||
|
|
||||||
|
|
||||||
class ToRepair(ActionWithMultipleDevices):
|
class ToRepair(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.ToRepair.__doc__
|
__doc__ = m.ToRepair.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Repair(ActionWithMultipleDevices):
|
class Repair(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.Repair.__doc__
|
__doc__ = m.Repair.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Ready(ActionWithMultipleDevices):
|
class Ready(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.Ready.__doc__
|
__doc__ = m.Ready.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
@ -472,15 +481,15 @@ class Management(ActionStatus):
|
||||||
__doc__ = m.Management.__doc__
|
__doc__ = m.Management.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ToPrepare(ActionWithMultipleDevices):
|
class ToPrepare(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.ToPrepare.__doc__
|
__doc__ = m.ToPrepare.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Prepare(ActionWithMultipleDevices):
|
class Prepare(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.Prepare.__doc__
|
__doc__ = m.Prepare.__doc__
|
||||||
|
|
||||||
|
|
||||||
class DataWipe(ActionWithMultipleDevices):
|
class DataWipe(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.DataWipe.__doc__
|
__doc__ = m.DataWipe.__doc__
|
||||||
document = NestedOn(s_generic_document.DataWipeDocument, only_query='id')
|
document = NestedOn(s_generic_document.DataWipeDocument, only_query='id')
|
||||||
|
|
||||||
|
@ -530,7 +539,7 @@ class Confirm(ActionWithMultipleDevices):
|
||||||
def validate_revoke(self, data: dict):
|
def validate_revoke(self, data: dict):
|
||||||
for dev in data['devices']:
|
for dev in data['devices']:
|
||||||
# if device not exist in the Trade, then this query is wrong
|
# if device not exist in the Trade, then this query is wrong
|
||||||
if not dev in data['action'].devices:
|
if dev not in data['action'].devices:
|
||||||
txt = "Device {} not exist in the trade".format(dev.devicehub_id)
|
txt = "Device {} not exist in the trade".format(dev.devicehub_id)
|
||||||
raise ValidationError(txt)
|
raise ValidationError(txt)
|
||||||
|
|
||||||
|
@ -543,13 +552,13 @@ class Revoke(ActionWithMultipleDevices):
|
||||||
def validate_revoke(self, data: dict):
|
def validate_revoke(self, data: dict):
|
||||||
for dev in data['devices']:
|
for dev in data['devices']:
|
||||||
# if device not exist in the Trade, then this query is wrong
|
# if device not exist in the Trade, then this query is wrong
|
||||||
if not dev in data['action'].devices:
|
if dev not in data['action'].devices:
|
||||||
txt = "Device {} not exist in the trade".format(dev.devicehub_id)
|
txt = "Device {} not exist in the trade".format(dev.devicehub_id)
|
||||||
raise ValidationError(txt)
|
raise ValidationError(txt)
|
||||||
|
|
||||||
for doc in data.get('documents', []):
|
for doc in data.get('documents', []):
|
||||||
# if document not exist in the Trade, then this query is wrong
|
# if document not exist in the Trade, then this query is wrong
|
||||||
if not doc in data['action'].documents:
|
if doc not in data['action'].documents:
|
||||||
txt = "Document {} not exist in the trade".format(doc.file_name)
|
txt = "Document {} not exist in the trade".format(doc.file_name)
|
||||||
raise ValidationError(txt)
|
raise ValidationError(txt)
|
||||||
|
|
||||||
|
@ -610,7 +619,7 @@ class ConfirmDocument(ActionWithMultipleDocuments):
|
||||||
if not doc.actions:
|
if not doc.actions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not doc.trading == 'Need Confirmation':
|
if not doc.trading == 'Need Confirmation':
|
||||||
txt = 'No there are documents to confirm'
|
txt = 'No there are documents to confirm'
|
||||||
raise ValidationError(txt)
|
raise ValidationError(txt)
|
||||||
|
|
||||||
|
@ -637,7 +646,7 @@ class RevokeDocument(ActionWithMultipleDocuments):
|
||||||
if not doc.actions:
|
if not doc.actions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not doc.trading in ['Document Confirmed', 'Confirm']:
|
if doc.trading not in ['Document Confirmed', 'Confirm']:
|
||||||
txt = 'No there are documents to revoke'
|
txt = 'No there are documents to revoke'
|
||||||
raise ValidationError(txt)
|
raise ValidationError(txt)
|
||||||
|
|
||||||
|
@ -662,7 +671,6 @@ class ConfirmRevokeDocument(ActionWithMultipleDocuments):
|
||||||
if not doc.actions:
|
if not doc.actions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
if not doc.trading == 'Revoke':
|
if not doc.trading == 'Revoke':
|
||||||
txt = 'No there are documents with revoke for confirm'
|
txt = 'No there are documents with revoke for confirm'
|
||||||
raise ValidationError(txt)
|
raise ValidationError(txt)
|
||||||
|
@ -827,6 +835,16 @@ class TransferOwnershipBlockchain(Trade):
|
||||||
__doc__ = m.TransferOwnershipBlockchain.__doc__
|
__doc__ = m.TransferOwnershipBlockchain.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
class Delete(ActionWithMultipleDevicesCheckingOwner):
|
||||||
|
__doc__ = m.Delete.__doc__
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def deactivate_device(self, data):
|
||||||
|
for dev in data['devices']:
|
||||||
|
if dev.last_action_trading is None:
|
||||||
|
dev.active = False
|
||||||
|
|
||||||
|
|
||||||
class Migrate(ActionWithMultipleDevices):
|
class Migrate(ActionWithMultipleDevices):
|
||||||
__doc__ = m.Migrate.__doc__
|
__doc__ = m.Migrate.__doc__
|
||||||
other = URL()
|
other = URL()
|
||||||
|
|
|
@ -127,6 +127,7 @@ class Device(Thing):
|
||||||
allocated.comment = "device is allocated or not."
|
allocated.comment = "device is allocated or not."
|
||||||
devicehub_id = db.Column(db.CIText(), nullable=True, unique=True, default=create_code)
|
devicehub_id = db.Column(db.CIText(), nullable=True, unique=True, default=create_code)
|
||||||
devicehub_id.comment = "device have a unique code."
|
devicehub_id.comment = "device have a unique code."
|
||||||
|
active = db.Column(Boolean, default=True)
|
||||||
|
|
||||||
_NON_PHYSICAL_PROPS = {
|
_NON_PHYSICAL_PROPS = {
|
||||||
'id',
|
'id',
|
||||||
|
@ -150,7 +151,8 @@ class Device(Thing):
|
||||||
'sku',
|
'sku',
|
||||||
'image',
|
'image',
|
||||||
'allocated',
|
'allocated',
|
||||||
'devicehub_id'
|
'devicehub_id',
|
||||||
|
'active'
|
||||||
}
|
}
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
|
|
@ -154,7 +154,7 @@ class Sync:
|
||||||
db_device = None
|
db_device = None
|
||||||
if device.hid:
|
if device.hid:
|
||||||
with suppress(ResourceNotFound):
|
with suppress(ResourceNotFound):
|
||||||
db_device = Device.query.filter_by(hid=device.hid, owner_id=g.user.id).one()
|
db_device = Device.query.filter_by(hid=device.hid, owner_id=g.user.id, active=True).one()
|
||||||
if db_device and db_device.allocated:
|
if db_device and db_device.allocated:
|
||||||
raise ResourceNotFound('device is actually allocated {}'.format(device))
|
raise ResourceNotFound('device is actually allocated {}'.format(device))
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -104,7 +104,7 @@ class DeviceView(View):
|
||||||
return super().get(id)
|
return super().get(id)
|
||||||
|
|
||||||
def patch(self, id):
|
def patch(self, id):
|
||||||
dev = Device.query.filter_by(id=id, owner_id=g.user.id).one()
|
dev = Device.query.filter_by(id=id, owner_id=g.user.id, active=True).one()
|
||||||
if isinstance(dev, Computer):
|
if isinstance(dev, Computer):
|
||||||
resource_def = app.resources['Computer']
|
resource_def = app.resources['Computer']
|
||||||
# TODO check how to handle the 'actions_one'
|
# TODO check how to handle the 'actions_one'
|
||||||
|
@ -129,12 +129,12 @@ class DeviceView(View):
|
||||||
return self.one_private(id)
|
return self.one_private(id)
|
||||||
|
|
||||||
def one_public(self, id: int):
|
def one_public(self, id: int):
|
||||||
device = Device.query.filter_by(devicehub_id=id).one()
|
device = Device.query.filter_by(devicehub_id=id, active=True).one()
|
||||||
return render_template('devices/layout.html', device=device, states=states)
|
return render_template('devices/layout.html', device=device, states=states)
|
||||||
|
|
||||||
@auth.Auth.requires_auth
|
@auth.Auth.requires_auth
|
||||||
def one_private(self, id: str):
|
def one_private(self, id: str):
|
||||||
device = Device.query.filter_by(devicehub_id=id, owner_id=g.user.id).first()
|
device = Device.query.filter_by(devicehub_id=id, owner_id=g.user.id, active=True).first()
|
||||||
if not device:
|
if not device:
|
||||||
return self.one_public(id)
|
return self.one_public(id)
|
||||||
return self.schema.jsonify(device)
|
return self.schema.jsonify(device)
|
||||||
|
@ -158,7 +158,7 @@ class DeviceView(View):
|
||||||
|
|
||||||
trades_dev_ids = {d.id for t in trades for d in t.devices}
|
trades_dev_ids = {d.id for t in trades for d in t.devices}
|
||||||
|
|
||||||
query = Device.query.filter(
|
query = Device.query.filter(Device.active == True).filter(
|
||||||
(Device.owner_id == g.user.id) | (Device.id.in_(trades_dev_ids))
|
(Device.owner_id == g.user.id) | (Device.id.in_(trades_dev_ids))
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
device:
|
||||||
|
manufacturer: p1
|
||||||
|
serialNumber: p1
|
||||||
|
model: p1
|
||||||
|
type: Desktop
|
||||||
|
chassis: Tower
|
||||||
|
components:
|
||||||
|
- manufacturer: p1c1m
|
||||||
|
serialNumber: p1c1s
|
||||||
|
type: Motherboard
|
||||||
|
- manufacturer: p1c2m
|
||||||
|
serialNumber: p1c2s
|
||||||
|
model: p1c2
|
||||||
|
speed: 1.23
|
||||||
|
cores: 2
|
||||||
|
type: Processor
|
||||||
|
actions:
|
||||||
|
- type: BenchmarkProcessor
|
||||||
|
rate: 1
|
||||||
|
elapsed: 166
|
||||||
|
- manufacturer: p1c3m
|
||||||
|
serialNumber: p1c3s
|
||||||
|
type: GraphicCard
|
||||||
|
memory: 1.5
|
||||||
|
elapsed: 25
|
||||||
|
software: Workbench
|
||||||
|
uuid: 77860eca-c3fd-41f6-a801-6af7bd8cf832
|
||||||
|
version: '11.0'
|
||||||
|
type: Snapshot
|
|
@ -2820,6 +2820,75 @@ def test_moveOnDocument(user: UserClient, user2: UserClient):
|
||||||
user.post(res=models.Action, data=request_moveOn, status=422)
|
user.post(res=models.Action, data=request_moveOn, status=422)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_delete_devices(user: UserClient):
|
||||||
|
"""This action deactive one device and simulate than one devices is delete."""
|
||||||
|
|
||||||
|
snap, _ = user.post(file('acer.happy.battery.snapshot'), res=models.Snapshot)
|
||||||
|
request = {'type': 'Delete', 'devices': [snap['device']['id']], 'name': 'borrado universal', 'severity': 'Info', 'description': 'duplicity of devices', 'endTime': '2021-07-07T22:00:00.000Z'}
|
||||||
|
|
||||||
|
action, _ = user.post(res=models.Action, data=request)
|
||||||
|
|
||||||
|
# Check get one device
|
||||||
|
user.get(res=Device, item=snap['device']['devicehubID'], status=404)
|
||||||
|
db_device = Device.query.filter_by(id=snap['device']['id']).one()
|
||||||
|
|
||||||
|
action_delete = sorted(db_device.actions, key=lambda x: x.created)[-1]
|
||||||
|
|
||||||
|
assert action_delete.t == 'Delete'
|
||||||
|
assert str(action_delete.id) == action['id']
|
||||||
|
assert db_device.active == False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Check use of filter from frontend
|
||||||
|
url = '/devices/?filter={"type":["Computer"]}'
|
||||||
|
|
||||||
|
devices, res = user.get(url, None)
|
||||||
|
assert len(devices['items']) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_delete_devices_check_sync(user: UserClient):
|
||||||
|
"""This action deactive one device and simulate than one devices is delete."""
|
||||||
|
|
||||||
|
file_snap1 = file('1-device-with-components.snapshot')
|
||||||
|
file_snap2 = file('2-device-with-components.snapshot')
|
||||||
|
snap, _ = user.post(file_snap1, res=models.Snapshot)
|
||||||
|
request = {'type': 'Delete', 'devices': [snap['device']['id']], 'name': 'borrado universal', 'severity': 'Info', 'description': 'duplicity of devices', 'endTime': '2021-07-07T22:00:00.000Z'}
|
||||||
|
|
||||||
|
action, _ = user.post(res=models.Action, data=request)
|
||||||
|
|
||||||
|
device1 = Device.query.filter_by(id=snap['device']['id']).one()
|
||||||
|
|
||||||
|
snap2, _ = user.post(file_snap2, res=models.Snapshot)
|
||||||
|
request2 = {'type': 'Delete', 'devices': [snap2['device']['id']], 'name': 'borrado universal', 'severity': 'Info', 'description': 'duplicity of devices', 'endTime': '2021-07-07T22:00:00.000Z'}
|
||||||
|
|
||||||
|
action2, _ = user.post(res=models.Action, data=request2)
|
||||||
|
|
||||||
|
device2 = Device.query.filter_by(id=snap2['device']['id']).one()
|
||||||
|
# check than device2 is an other device than device1
|
||||||
|
assert device2.id != device1.id
|
||||||
|
# check than device2 have the components of device1
|
||||||
|
assert len([x for x in device2.components
|
||||||
|
if device1.id in [y.device.id for y in x.actions if hasattr(y, 'device')]]) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_delete_devices_permitions(user: UserClient, user2: UserClient):
|
||||||
|
"""This action deactive one device and simulate than one devices is delete."""
|
||||||
|
|
||||||
|
file_snap = file('1-device-with-components.snapshot')
|
||||||
|
snap, _ = user.post(file_snap, res=models.Snapshot)
|
||||||
|
device = Device.query.filter_by(id=snap['device']['id']).one()
|
||||||
|
|
||||||
|
request = {'type': 'Delete', 'devices': [snap['device']['id']], 'name': 'borrado universal', 'severity': 'Info', 'description': 'duplicity of devices', 'endTime': '2021-07-07T22:00:00.000Z'}
|
||||||
|
action, _ = user2.post(res=models.Action, data=request, status=422)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.mvp
|
@pytest.mark.mvp
|
||||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
def test_moveOnDocument_bug168(user: UserClient, user2: UserClient):
|
def test_moveOnDocument_bug168(user: UserClient, user2: UserClient):
|
||||||
|
|
|
@ -59,59 +59,6 @@ def test_api_docs(client: Client):
|
||||||
'/users/',
|
'/users/',
|
||||||
'/users/login/',
|
'/users/login/',
|
||||||
'/users/logout/',
|
'/users/logout/',
|
||||||
# '/devices/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/batteries/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/bikes/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/cameras/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/cellphones/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/components/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/computer-accessories/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/computer-monitors/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/computers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/cookings/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/data-storages/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/dehumidifiers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/desktops/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/displays/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/diy-and-gardenings/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/drills/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/graphic-cards/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/hard-drives/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/homes/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/hubs/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/keyboards/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/label-printers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/laptops/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/memory-card-readers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/mice/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/microphones/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/mixers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/mobiles/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/monitors/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/motherboards/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/network-adapters/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/networkings/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/pack-of-screwdrivers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/printers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/processors/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/rackets/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/ram-modules/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/recreations/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/routers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/sais/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/servers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/smartphones/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/solid-state-drives/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/sound-cards/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/sounds/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/stairs/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/switches/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/tablets/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/television-sets/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/video-scalers/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/videoconferences/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/videos/{dev1_id}/merge/{dev2_id}',
|
|
||||||
# '/wireless-access-points/{dev1_id}/merge/{dev2_id}',
|
|
||||||
}
|
}
|
||||||
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
|
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
|
||||||
assert docs['components']['securitySchemes']['bearerAuth'] == {
|
assert docs['components']['securitySchemes']['bearerAuth'] == {
|
||||||
|
@ -122,4 +69,4 @@ def test_api_docs(client: Client):
|
||||||
'scheme': 'basic',
|
'scheme': 'basic',
|
||||||
'name': 'Authorization'
|
'name': 'Authorization'
|
||||||
}
|
}
|
||||||
assert len(docs['definitions']) == 131
|
assert len(docs['definitions']) == 132
|
||||||
|
|
Reference in New Issue