diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py index 3224dfd3..aaafb9f3 100644 --- a/ereuse_devicehub/client.py +++ b/ereuse_devicehub/client.py @@ -2,7 +2,10 @@ from inspect import isclass from typing import Dict, Iterable, Type, Union from ereuse_utils.test import JSON, Res -from teal.client import Client as TealClient, Query, Status +from flask.testing import FlaskClient +from flask_wtf.csrf import generate_csrf +from teal.client import Client as TealClient +from teal.client import Query, Status from werkzeug.exceptions import HTTPException from ereuse_devicehub.resources import models, schemas @@ -13,110 +16,156 @@ ResourceLike = Union[Type[Union[models.Thing, schemas.Thing]], str] class Client(TealClient): """A client suited for Devicehub main usage.""" - def __init__(self, application, - response_wrapper=None, - use_cookies=False, - allow_subdomain_redirects=False): - super().__init__(application, response_wrapper, use_cookies, allow_subdomain_redirects) + def __init__( + self, + application, + response_wrapper=None, + use_cookies=False, + allow_subdomain_redirects=False, + ): + super().__init__( + application, response_wrapper, use_cookies, allow_subdomain_redirects + ) - def open(self, - uri: str, - res: ResourceLike = None, - status: Status = 200, - query: Query = tuple(), - accept=JSON, - content_type=JSON, - item=None, - headers: dict = None, - token: str = None, - **kw) -> Res: + def open( + self, + uri: str, + res: ResourceLike = None, + status: Status = 200, + query: Query = tuple(), + accept=JSON, + content_type=JSON, + item=None, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: if isclass(res) and issubclass(res, (models.Thing, schemas.Thing)): res = res.t - return super().open(uri, res, status, query, accept, content_type, item, headers, token, - **kw) + return super().open( + uri, res, status, query, accept, content_type, item, headers, token, **kw + ) - def get(self, - uri: str = '', - res: ResourceLike = None, - query: Query = tuple(), - status: Status = 200, - item: Union[int, str] = None, - accept: str = JSON, - headers: dict = None, - token: str = None, - **kw) -> Res: + def get( + self, + uri: str = '', + res: ResourceLike = None, + query: Query = tuple(), + status: Status = 200, + item: Union[int, str] = None, + accept: str = JSON, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: return super().get(uri, res, query, status, item, accept, headers, token, **kw) - def post(self, - data: str or dict, - uri: str = '', - res: ResourceLike = None, - query: Query = tuple(), - status: Status = 201, - content_type: str = JSON, - accept: str = JSON, - headers: dict = None, - token: str = None, - **kw) -> Res: - return super().post(data, uri, res, query, status, content_type, accept, headers, token, - **kw) + def post( + self, + data: str or dict, + uri: str = '', + res: ResourceLike = None, + query: Query = tuple(), + status: Status = 201, + content_type: str = JSON, + accept: str = JSON, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: + return super().post( + data, uri, res, query, status, content_type, accept, headers, token, **kw + ) - def patch(self, - data: str or dict, - uri: str = '', - res: ResourceLike = None, - query: Query = tuple(), - item: Union[int, str] = None, - status: Status = 200, - content_type: str = JSON, - accept: str = JSON, - headers: dict = None, - token: str = None, - **kw) -> Res: - return super().patch(data, uri, res, query, item, status, content_type, accept, token, - headers, **kw) + def patch( + self, + data: str or dict, + uri: str = '', + res: ResourceLike = None, + query: Query = tuple(), + item: Union[int, str] = None, + status: Status = 200, + content_type: str = JSON, + accept: str = JSON, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: + return super().patch( + data, + uri, + res, + query, + item, + status, + content_type, + accept, + token, + headers, + **kw, + ) - def put(self, - data: str or dict, - uri: str = '', - res: ResourceLike = None, - query: Query = tuple(), - item: Union[int, str] = None, - status: Status = 201, - content_type: str = JSON, - accept: str = JSON, - headers: dict = None, - token: str = None, - **kw) -> Res: - return super().put(data, uri, res, query, item, status, content_type, accept, token, - headers, **kw) + def put( + self, + data: str or dict, + uri: str = '', + res: ResourceLike = None, + query: Query = tuple(), + item: Union[int, str] = None, + status: Status = 201, + content_type: str = JSON, + accept: str = JSON, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: + return super().put( + data, + uri, + res, + query, + item, + status, + content_type, + accept, + token, + headers, + **kw, + ) - def delete(self, - uri: str = '', - res: ResourceLike = None, - query: Query = tuple(), - status: Status = 204, - item: Union[int, str] = None, - accept: str = JSON, - headers: dict = None, - token: str = None, - **kw) -> Res: - return super().delete(uri, res, query, status, item, accept, headers, token, **kw) + def delete( + self, + uri: str = '', + res: ResourceLike = None, + query: Query = tuple(), + status: Status = 204, + item: Union[int, str] = None, + accept: str = JSON, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: + return super().delete( + uri, res, query, status, item, accept, headers, token, **kw + ) def login(self, email: str, password: str): assert isinstance(email, str) assert isinstance(password, str) - return self.post({'email': email, 'password': password}, '/users/login/', status=200) + return self.post( + {'email': email, 'password': password}, '/users/login/', status=200 + ) - def get_many(self, - res: ResourceLike, - resources: Iterable[Union[dict, int]], - key: str = None, - **kw) -> Iterable[Union[Dict[str, object], str]]: + def get_many( + self, + res: ResourceLike, + resources: Iterable[Union[dict, int]], + key: str = None, + **kw, + ) -> Iterable[Union[Dict[str, object], str]]: """Like :meth:`.get` but with many resources.""" return ( - self.get(res=res, item=r[key] if key else r, **kw)[0] - for r in resources + self.get(res=res, item=r[key] if key else r, **kw)[0] for r in resources ) @@ -126,33 +175,119 @@ class UserClient(Client): It will automatically perform login on the first request. """ - def __init__(self, application, - email: str, - password: str, - response_wrapper=None, - use_cookies=False, - allow_subdomain_redirects=False): - super().__init__(application, response_wrapper, use_cookies, allow_subdomain_redirects) + def __init__( + self, + application, + email: str, + password: str, + response_wrapper=None, + use_cookies=False, + allow_subdomain_redirects=False, + ): + super().__init__( + application, response_wrapper, use_cookies, allow_subdomain_redirects + ) self.email = email # type: str self.password = password # type: str self.user = None # type: dict - def open(self, - uri: str, - res: ResourceLike = None, - status: int or HTTPException = 200, - query: Query = tuple(), - accept=JSON, - content_type=JSON, - item=None, - headers: dict = None, - token: str = None, - **kw) -> Res: - return super().open(uri, res, status, query, accept, content_type, item, headers, - self.user['token'] if self.user else token, **kw) + def open( + self, + uri: str, + res: ResourceLike = None, + status: int or HTTPException = 200, + query: Query = tuple(), + accept=JSON, + content_type=JSON, + item=None, + headers: dict = None, + token: str = None, + **kw, + ) -> Res: + return super().open( + uri, + res, + status, + query, + accept, + content_type, + item, + headers, + self.user['token'] if self.user else token, + **kw, + ) # noinspection PyMethodOverriding def login(self): response = super().login(self.email, self.password) self.user = response[0] return response + + +class UserClientFlask: + def __init__( + self, + application, + email: str, + password: str, + response_wrapper=None, + use_cookies=True, + follow_redirects=True, + ): + self.email = email + self.password = password + self.follow_redirects = follow_redirects + self.user = None + + self.client = FlaskClient(application, use_cookies=use_cookies) + self.client.get('/login/') + + data = { + 'email': email, + 'password': password, + 'csrf_token': generate_csrf(), + } + body, status, headers = self.client.post( + '/login/', data=data, follow_redirects=True + ) + self.headers = headers + body = next(body).decode("utf-8") + assert "Unassgined" in body + + def get( + self, + uri='', + data=None, + follow_redirects=True, + content_type='text/html; charset=utf-8', + decode=True, + **kw, + ): + + body, status, headers = self.client.get( + uri, data=data, follow_redirects=follow_redirects, headers=self.headers + ) + if decode: + body = next(body).decode("utf-8") + return (body, status) + + def post( + self, + uri='', + data=None, + follow_redirects=True, + content_type='application/x-www-form-urlencoded', + decode=True, + **kw, + ): + + body, status, headers = self.client.post( + uri, + data=data, + follow_redirects=follow_redirects, + headers=self.headers, + content_type=content_type, + ) + if decode: + body = next(body).decode("utf-8") + return (body, status) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 72f033f8..08b6eeca 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -497,7 +497,7 @@ class TagDeviceForm(FlaskForm): db.session.commit() -class NewActionForm(FlaskForm): +class ActionFormMix(FlaskForm): name = StringField( 'Name', [validators.length(max=50)], @@ -529,17 +529,23 @@ class NewActionForm(FlaskForm): if not is_valid: return False - self._devices = OrderedSet() - if self.devices.data: - devices = set(self.devices.data.split(",")) - self._devices = OrderedSet( - Device.query.filter(Device.id.in_(devices)) - .filter(Device.owner_id == g.user.id) - .all() - ) + if self.type.data in [None, '']: + return False - if not self._devices: - return False + if not self.devices.data: + return False + + self._devices = OrderedSet() + + devices = set(self.devices.data.split(",")) + self._devices = OrderedSet( + Device.query.filter(Device.id.in_(devices)) + .filter(Device.owner_id == g.user.id) + .all() + ) + + if not self._devices: + return False return True @@ -572,7 +578,20 @@ class NewActionForm(FlaskForm): return self.type.data -class AllocateForm(NewActionForm): +class NewActionForm(ActionFormMix): + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + if not is_valid: + return False + + if self.type.data in ['Allocate', 'Deallocate', 'Trade', 'DataWipe']: + return False + + return True + + +class AllocateForm(ActionFormMix): start_time = DateField('Start time') end_time = DateField('End time') final_user_code = StringField('Final user code', [validators.length(max=50)]) @@ -582,6 +601,9 @@ class AllocateForm(NewActionForm): def validate(self, extra_validators=None): is_valid = super().validate(extra_validators) + if self.type.data not in ['Allocate', 'Deallocate']: + return False + start_time = self.start_time.data end_time = self.end_time.data if start_time and end_time and end_time < start_time: @@ -650,7 +672,7 @@ class DataWipeDocumentForm(Form): return self._obj -class DataWipeForm(NewActionForm): +class DataWipeForm(ActionFormMix): document = FormField(DataWipeDocumentForm) def save(self): @@ -677,7 +699,7 @@ class DataWipeForm(NewActionForm): return self.instance -class TradeForm(NewActionForm): +class TradeForm(ActionFormMix): user_from = StringField( 'Supplier', [validators.Optional()], @@ -724,6 +746,9 @@ class TradeForm(NewActionForm): email_from = self.user_from.data email_to = self.user_to.data + if self.type.data != "Trade": + return False + if not self.confirm.data and not self.code.data: self.code.errors = ["If you don't want to confirm, you need a code"] is_valid = False diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 056b99b7..8cb44b8a 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -303,16 +303,19 @@ class NewActionView(View): def dispatch_request(self): self.form = self.form_class() + next_url = self.get_next_url() if self.form.validate_on_submit(): self.form.save() messages.success( 'Action "{}" created successfully!'.format(self.form.type.data) ) - next_url = self.get_next_url() return flask.redirect(next_url) + messages.error('Action {} error!'.format(self.form.type.data)) + return flask.redirect(next_url) + def get_next_url(self): lot_id = self.form.lot.data @@ -338,10 +341,12 @@ class NewAllocateView(NewActionView, DeviceListMix): next_url = self.get_next_url() return flask.redirect(next_url) - lot_id = self.form.lot.data - self.get_context(lot_id) - self.context['form_new_allocate'] = self.form - return flask.render_template(self.template_name, **self.context) + messages.error('Action {} error!'.format(self.form.type.data)) + for k, v in self.form.errors.items(): + value = ';'.join(v) + messages.error('Action Error {key}: {value}!'.format(key=k, value=value)) + next_url = self.get_next_url() + return flask.redirect(next_url) class NewDataWipeView(NewActionView, DeviceListMix): @@ -360,10 +365,9 @@ class NewDataWipeView(NewActionView, DeviceListMix): next_url = self.get_next_url() return flask.redirect(next_url) - lot_id = self.form.lot.data - self.get_context(lot_id) - self.context['form_new_datawipe'] = self.form - return flask.render_template(self.template_name, **self.context) + messages.error('Action {} error!'.format(self.form.type.data)) + next_url = self.get_next_url() + return flask.redirect(next_url) class NewTradeView(NewActionView, DeviceListMix): @@ -382,10 +386,9 @@ class NewTradeView(NewActionView, DeviceListMix): next_url = self.get_next_url() return flask.redirect(next_url) - lot_id = self.form.lot.data - self.get_context(lot_id) - self.context['form_new_trade'] = self.form - return flask.render_template(self.template_name, **self.context) + messages.error('Action {} error!'.format(self.form.type.data)) + next_url = self.get_next_url() + return flask.redirect(next_url) class NewTradeDocumentView(View): diff --git a/tests/conftest.py b/tests/conftest.py index 991374ba..a8d04506 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,27 +1,29 @@ import io import uuid -import jwt -import ereuse_utils from contextlib import redirect_stdout from datetime import datetime from pathlib import Path -from decouple import config import boltons.urlutils +import ereuse_utils +import jwt import pytest import yaml +from decouple import config from psycopg2 import IntegrityError from sqlalchemy.exc import ProgrammingError -from ereuse_devicehub.client import Client, UserClient +from ereuse_devicehub.client import Client, UserClient, UserClientFlask from ereuse_devicehub.config import DevicehubConfig from ereuse_devicehub.db import db from ereuse_devicehub.devicehub import Devicehub +from ereuse_devicehub.inventory.views import devices +from ereuse_devicehub.labels.views import labels from ereuse_devicehub.resources.agent.models import Person -from ereuse_devicehub.resources.tag import Tag -from ereuse_devicehub.resources.user.models import User -from ereuse_devicehub.resources.user.models import Session from ereuse_devicehub.resources.enums import SessionType +from ereuse_devicehub.resources.tag import Tag +from ereuse_devicehub.resources.user.models import Session, User +from ereuse_devicehub.views import core STARTT = datetime(year=2000, month=1, day=1, hour=1) """A dummy starting time to use in tests.""" @@ -50,6 +52,20 @@ def config(): @pytest.fixture(scope='session') def _app(config: TestConfig) -> Devicehub: + # dh_config = DevicehubConfig() + # config = TestConfig(dh_config) + app = Devicehub(inventory='test', config=config, db=db) + app.register_blueprint(core) + app.register_blueprint(devices) + app.register_blueprint(labels) + app.config["SQLALCHEMY_RECORD_QUERIES"] = True + app.config['PROFILE'] = True + # app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30]) + return app + + +@pytest.fixture(scope='session') +def _app2(config: TestConfig) -> Devicehub: return Devicehub(inventory='test', config=config, db=db) @@ -61,13 +77,15 @@ def app(request, _app: Devicehub) -> Devicehub: db.drop_all() def _init(): - _app.init_db(name='Test Inventory', - org_name='FooOrg', - org_id='foo-org-id', - tag_url=boltons.urlutils.URL('https://example.com'), - tag_token=uuid.UUID('52dacef0-6bcb-4919-bfed-f10d2c96ecee'), - erase=False, - common=True) + _app.init_db( + name='Test Inventory', + org_name='FooOrg', + org_id='foo-org-id', + tag_url=boltons.urlutils.URL('https://example.com'), + tag_token=uuid.UUID('52dacef0-6bcb-4919-bfed-f10d2c96ecee'), + erase=False, + common=True, + ) with _app.app_context(): try: @@ -99,7 +117,9 @@ def user(app: Devicehub) -> UserClient: with app.app_context(): password = 'foo' user = create_user(password=password) - client = UserClient(app, user.email, password, response_wrapper=app.response_class) + client = UserClient( + app, user.email, password, response_wrapper=app.response_class + ) client.login() return client @@ -111,11 +131,34 @@ def user2(app: Devicehub) -> UserClient: password = 'foo' email = 'foo2@foo.com' user = create_user(email=email, password=password) - client = UserClient(app, user.email, password, response_wrapper=app.response_class) + client = UserClient( + app, user.email, password, response_wrapper=app.response_class + ) client.login() return client +@pytest.fixture() +def user3(app: Devicehub) -> UserClientFlask: + """Gets a client with a logged-in dummy user.""" + with app.app_context(): + password = 'foo' + user = create_user(password=password) + client = UserClientFlask(app, user.email, password) + return client + + +@pytest.fixture() +def user4(app: Devicehub) -> UserClient: + """Gets a client with a logged-in dummy user.""" + with app.app_context(): + password = 'foo' + email = 'foo2@foo.com' + user = create_user(email=email, password=password) + client = UserClientFlask(app, user.email, password) + return client + + def create_user(email='foo@foo.com', password='foo') -> User: user = User(email=email, password=password) user.individuals.add(Person(name='Timmy')) @@ -145,16 +188,13 @@ def auth_app_context(app: Devicehub): def json_encode(dev: str) -> dict: """Encode json.""" data = {"type": "Snapshot"} - data['data'] = jwt.encode(dev, - P, - algorithm="HS256", - json_encoder=ereuse_utils.JSONEncoder + data['data'] = jwt.encode( + dev, P, algorithm="HS256", json_encoder=ereuse_utils.JSONEncoder ) return data - def yaml2json(name: str) -> dict: """Opens and parses a YAML file from the ``files`` subdir.""" with Path(__file__).parent.joinpath('files').joinpath(name + '.yaml').open() as f: @@ -168,7 +208,9 @@ def file(name: str) -> dict: def file_workbench(name: str) -> dict: """Opens and parses a YAML file from the ``files`` subdir.""" - with Path(__file__).parent.joinpath('workbench_files').joinpath(name + '.json').open() as f: + with Path(__file__).parent.joinpath('workbench_files').joinpath( + name + '.json' + ).open() as f: return yaml.load(f) diff --git a/tests/files/export_devices.csv b/tests/files/export_devices.csv new file mode 100644 index 00000000..586a25e7 --- /dev/null +++ b/tests/files/export_devices.csv @@ -0,0 +1,2 @@ +DHID;DocumentID;Public Link;Lots;Tag 1 Type;Tag 1 ID;Tag 1 Organization;Tag 2 Type;Tag 2 ID;Tag 2 Organization;Tag 3 Type;Tag 3 ID;Tag 3 Organization;Device Hardware ID;Device Type;Device Chassis;Device Serial Number;Device Model;Device Manufacturer;Registered in;Registered (process);Updated in (software);Updated in (web);Physical state;Trading state;Processor;RAM (MB);Data Storage Size (MB);Processor 1;Processor 1 Manufacturer;Processor 1 Model;Processor 1 Serial Number;Processor 1 Number of cores;Processor 1 Speed (GHz);Benchmark Processor 1 (points);Benchmark ProcessorSysbench Processor 1 (points);Processor 2;Processor 2 Manufacturer;Processor 2 Model;Processor 2 Serial Number;Processor 2 Number of cores;Processor 2 Speed (GHz);Benchmark Processor 2 (points);Benchmark ProcessorSysbench Processor 2 (points);RamModule 1;RamModule 1 Manufacturer;RamModule 1 Model;RamModule 1 Serial Number;RamModule 1 Size (MB);RamModule 1 Speed (MHz);RamModule 2;RamModule 2 Manufacturer;RamModule 2 Model;RamModule 2 Serial Number;RamModule 2 Size (MB);RamModule 2 Speed (MHz);RamModule 3;RamModule 3 Manufacturer;RamModule 3 Model;RamModule 3 Serial Number;RamModule 3 Size (MB);RamModule 3 Speed (MHz);RamModule 4;RamModule 4 Manufacturer;RamModule 4 Model;RamModule 4 Serial Number;RamModule 4 Size (MB);RamModule 4 Speed (MHz);DataStorage 1;DataStorage 1 Manufacturer;DataStorage 1 Model;DataStorage 1 Serial Number;DataStorage 1 Size (MB);Erasure DataStorage 1;Erasure DataStorage 1 Serial Number;Erasure DataStorage 1 Size (MB);Erasure DataStorage 1 Software;Erasure DataStorage 1 Result;Erasure DataStorage 1 Certificate URL;Erasure DataStorage 1 Type;Erasure DataStorage 1 Method;Erasure DataStorage 1 Elapsed (hours);Erasure DataStorage 1 Date;Erasure DataStorage 1 Steps;Erasure DataStorage 1 Steps Start Time;Erasure DataStorage 1 Steps End Time;Benchmark DataStorage 1 Read Speed (MB/s);Benchmark DataStorage 1 Writing speed (MB/s);Test DataStorage 1 Software;Test DataStorage 1 Type;Test DataStorage 1 Result;Test DataStorage 1 Power cycle count;Test DataStorage 1 Lifetime (days);Test DataStorage 1 Power on hours;DataStorage 2;DataStorage 2 Manufacturer;DataStorage 2 Model;DataStorage 2 Serial Number;DataStorage 2 Size (MB);Erasure DataStorage 2;Erasure DataStorage 2 Serial Number;Erasure DataStorage 2 Size (MB);Erasure DataStorage 2 Software;Erasure DataStorage 2 Result;Erasure DataStorage 2 Certificate URL;Erasure DataStorage 2 Type;Erasure DataStorage 2 Method;Erasure DataStorage 2 Elapsed (hours);Erasure DataStorage 2 Date;Erasure DataStorage 2 Steps;Erasure DataStorage 2 Steps Start Time;Erasure DataStorage 2 Steps End Time;Benchmark DataStorage 2 Read Speed (MB/s);Benchmark DataStorage 2 Writing speed (MB/s);Test DataStorage 2 Software;Test DataStorage 2 Type;Test DataStorage 2 Result;Test DataStorage 2 Power cycle count;Test DataStorage 2 Lifetime (days);Test DataStorage 2 Power on hours;DataStorage 3;DataStorage 3 Manufacturer;DataStorage 3 Model;DataStorage 3 Serial Number;DataStorage 3 Size (MB);Erasure DataStorage 3;Erasure DataStorage 3 Serial Number;Erasure DataStorage 3 Size (MB);Erasure DataStorage 3 Software;Erasure DataStorage 3 Result;Erasure DataStorage 3 Certificate URL;Erasure DataStorage 3 Type;Erasure DataStorage 3 Method;Erasure DataStorage 3 Elapsed (hours);Erasure DataStorage 3 Date;Erasure DataStorage 3 Steps;Erasure DataStorage 3 Steps Start Time;Erasure DataStorage 3 Steps End Time;Benchmark DataStorage 3 Read Speed (MB/s);Benchmark DataStorage 3 Writing speed (MB/s);Test DataStorage 3 Software;Test DataStorage 3 Type;Test DataStorage 3 Result;Test DataStorage 3 Power cycle count;Test DataStorage 3 Lifetime (days);Test DataStorage 3 Power on hours;DataStorage 4;DataStorage 4 Manufacturer;DataStorage 4 Model;DataStorage 4 Serial Number;DataStorage 4 Size (MB);Erasure DataStorage 4;Erasure DataStorage 4 Serial Number;Erasure DataStorage 4 Size (MB);Erasure DataStorage 4 Software;Erasure DataStorage 4 Result;Erasure DataStorage 4 Certificate URL;Erasure DataStorage 4 Type;Erasure DataStorage 4 Method;Erasure DataStorage 4 Elapsed (hours);Erasure DataStorage 4 Date;Erasure DataStorage 4 Steps;Erasure DataStorage 4 Steps Start Time;Erasure DataStorage 4 Steps End Time;Benchmark DataStorage 4 Read Speed (MB/s);Benchmark DataStorage 4 Writing speed (MB/s);Test DataStorage 4 Software;Test DataStorage 4 Type;Test DataStorage 4 Result;Test DataStorage 4 Power cycle count;Test DataStorage 4 Lifetime (days);Test DataStorage 4 Power on hours;Motherboard 1;Motherboard 1 Manufacturer;Motherboard 1 Model;Motherboard 1 Serial Number;Display 1;Display 1 Manufacturer;Display 1 Model;Display 1 Serial Number;GraphicCard 1;GraphicCard 1 Manufacturer;GraphicCard 1 Model;GraphicCard 1 Serial Number;GraphicCard 1 Memory (MB);GraphicCard 2;GraphicCard 2 Manufacturer;GraphicCard 2 Model;GraphicCard 2 Serial Number;GraphicCard 2 Memory (MB);NetworkAdapter 1;NetworkAdapter 1 Manufacturer;NetworkAdapter 1 Model;NetworkAdapter 1 Serial Number;NetworkAdapter 2;NetworkAdapter 2 Manufacturer;NetworkAdapter 2 Model;NetworkAdapter 2 Serial Number;SoundCard 1;SoundCard 1 Manufacturer;SoundCard 1 Model;SoundCard 1 Serial Number;SoundCard 2;SoundCard 2 Manufacturer;SoundCard 2 Model;SoundCard 2 Serial Number;Device Rate;Device Range;Processor Rate;Processor Range;RAM Rate;RAM Range;Data Storage Rate;Data Storage Range;Price;Benchmark RamSysbench (points) +O48N2;;http://localhost/devices/O48N2;;named;O48N2;FooOrg;;;;;;;laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b;Laptop;Netbook;b8oaas048285;1001pxd;asustek computer inc.;Tue Apr 19 18:13:44 2022;Workbench 11.0a2;2022-04-19 18:13:45.018710+02:00;;;;intel atom cpu n455 @ 2.66ghz;1024;238475;Processor 6: model intel atom cpu n455 @ 2.66ghz, S/N None;intel corp.;intel atom cpu n455 @ 2.66ghz;;1;2.667;6666.24;164.0803;;;;;;;;;RamModule 10: model None, S/N None;;;;1024;667;;;;;;;;;;;;;;;;;;;HardDrive 11: model hts54322, S/N e2024242cv86mm;hitachi;hts54322;e2024242cv86mm;238475;harddrive-hitachi-hts54322-e2024242cv86mm;e2024242cv86mm;238475;Workbench 11.0a2;Success;;EraseBasic;Shred;1:16:49;2022-04-19 18:13:44.975393+02:00;✓ – StepRandom 1:16:49;2018-07-03 11:15:22.257059+02:00;2018-07-03 12:32:11.843190+02:00;66.2;21.8;Workbench 11.0a2;Short;Failure;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Motherboard 12: model 1001pxd, S/N eee0123456720;asustek computer inc.;1001pxd;eee0123456720;;;;;GraphicCard 7: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None;intel corporation;atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller;;256;;;;;;NetworkAdapter 4: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9;qualcomm atheros;ar9285 wireless network adapter;74:2f:68:8b:fd:c9;NetworkAdapter 5: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b;qualcomm atheros;ar8152 v2.0 fast ethernet;14:da:e9:42:f6:7b;SoundCard 8: model nm10/ich7 family high definition audio controller, S/N None;intel corporation;nm10/ich7 family high definition audio controller;;SoundCard 9: model usb 2.0 uvc vga webcam, S/N 0x0001;azurewave;usb 2.0 uvc vga webcam;0x0001;1.75;LOW;1.55;LOW;1.53;LOW;3.76;HIGH;52.50 €;15.7188 diff --git a/tests/test_basic.py b/tests/test_basic.py index 974140b4..4329d996 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,7 +1,7 @@ import pytest -from ereuse_devicehub.devicehub import Devicehub from ereuse_devicehub.client import Client +from ereuse_devicehub.devicehub import Devicehub @pytest.mark.mvp @@ -28,37 +28,65 @@ def test_api_docs(client: Client): """Tests /apidocs correct initialization.""" docs, _ = client.get('/apidocs') assert set(docs['paths'].keys()) == { + '/', '/actions/', - '/apidocs', '/allocates/', + '/apidocs', '/deallocates/', '/deliverynotes/', '/devices/', '/devices/static/{filename}', - '/documents/static/{filename}', '/documents/actions/', - '/documents/erasures/', - '/documents/devices/', - '/documents/stamps/', - '/documents/wbconf/{wbtype}', - '/documents/internalstats/', - '/documents/stock/', '/documents/check/', + '/documents/devices/', + '/documents/erasures/', + '/documents/internalstats/', '/documents/lots/', - '/versions/', - '/manufacturers/', + '/documents/stamps/', + '/documents/static/{filename}', + '/documents/stock/', + '/documents/wbconf/{wbtype}', + '/inventory/action/add/', + '/inventory/action/allocate/add/', + '/inventory/action/datawipe/add/', + '/inventory/action/trade/add/', + '/inventory/device/', + '/inventory/device/add/', + '/inventory/device/{id}/', + '/inventory/export/{export_id}/', + '/inventory/lot/add/', + '/inventory/lot/{id}/', + '/inventory/lot/{id}/del/', + '/inventory/lot/{lot_id}/device/', + '/inventory/lot/{lot_id}/device/add/', + '/inventory/lot/{lot_id}/trade-document/add/', + '/inventory/lot/{lot_id}/upload-snapshot/', + '/inventory/tag/devices/add/', + '/inventory/tag/devices/{id}/del/', + '/inventory/upload-snapshot/', + '/labels/', + '/labels/add/', + '/labels/print', + '/labels/unnamed/add/', + '/labels/{id}/', '/licences/', '/lives/', + '/login/', + '/logout/', '/lots/', '/lots/{id}/children', '/lots/{id}/devices', + '/manufacturers/', '/metrics/', + '/profile/', + '/set_password/', '/tags/', '/tags/{tag_id}/device/{device_id}', '/trade-documents/', '/users/', '/users/login/', '/users/logout/', + '/versions/', } assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'} assert docs['components']['securitySchemes']['bearerAuth'] == { @@ -67,6 +95,6 @@ def test_api_docs(client: Client): 'description:': 'HTTP Basic scheme', 'type': 'http', 'scheme': 'basic', - 'name': 'Authorization' + 'name': 'Authorization', } assert len(docs['definitions']) == 132 diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py new file mode 100644 index 00000000..0df56652 --- /dev/null +++ b/tests/test_render_2_0.py @@ -0,0 +1,856 @@ +import csv +import json +from io import BytesIO +from pathlib import Path + +import pytest +from flask.testing import FlaskClient +from flask_wtf.csrf import generate_csrf + +from ereuse_devicehub.client import UserClient, UserClientFlask +from ereuse_devicehub.devicehub import Devicehub +from ereuse_devicehub.resources.action.models import Snapshot +from ereuse_devicehub.resources.device.models import Device +from ereuse_devicehub.resources.lot.models import Lot +from tests import conftest + + +def create_device(user, file_name): + uri = '/inventory/upload-snapshot/' + snapshot = conftest.yaml2json(file_name.split(".json")[0]) + b_snapshot = bytes(json.dumps(snapshot), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + user.get(uri) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + user.post(uri, data=data, content_type="multipart/form-data") + + return Snapshot.query.one() + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_login(user: UserClient, app: Devicehub): + """Checks a simple login""" + + client = FlaskClient(app, use_cookies=True) + + body, status, headers = client.get('/login/') + body = next(body).decode("utf-8") + assert status == '200 OK' + assert "Login to Your Account" in body + + data = { + 'email': user.email, + 'password': 'foo', + 'remember': False, + 'csrf_token': generate_csrf(), + } + body, status, headers = client.post('/login/', data=data, follow_redirects=True) + + body = next(body).decode("utf-8") + assert status == '200 OK' + assert "Login to Your Account" not in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_profile(user3: UserClientFlask): + body, status = user3.get('/profile/') + + assert status == '200 OK' + assert "Profile" in body + assert user3.email in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_inventory(user3: UserClientFlask): + body, status = user3.get('/inventory/device/') + + assert status == '200 OK' + assert "Unassgined" in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_add_lot(user3: UserClientFlask): + body, status = user3.get('/inventory/lot/add/') + + lot_name = "lot1" + assert status == '200 OK' + assert "Add a new lot" in body + assert lot_name not in body + + data = { + 'name': lot_name, + 'csrf_token': generate_csrf(), + } + body, status = user3.post('/inventory/lot/add/', data=data) + + assert status == '200 OK' + assert lot_name in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_del_lot(user3: UserClientFlask): + body, status = user3.get('/inventory/lot/add/') + + lot_name = "lot1" + assert status == '200 OK' + assert "Add a new lot" in body + assert lot_name not in body + + data = { + 'name': lot_name, + 'csrf_token': generate_csrf(), + } + body, status = user3.post('/inventory/lot/add/', data=data) + + assert status == '200 OK' + assert lot_name in body + + lot = Lot.query.filter_by(name=lot_name).one() + uri = '/inventory/lot/{id}/del/'.format(id=lot.id) + body, status = user3.get(uri) + assert lot_name not in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_update_lot(user3: UserClientFlask): + user3.get('/inventory/lot/add/') + + # Add lot + data = { + 'name': "lot1", + 'csrf_token': generate_csrf(), + } + user3.post('/inventory/lot/add/', data=data) + + data = { + 'name': "lot2", + 'csrf_token': generate_csrf(), + } + + lot = Lot.query.one() + uri = '/inventory/lot/{uuid}/'.format(uuid=lot.id) + body, status = user3.post(uri, data=data) + + assert status == '200 OK' + assert "lot2" in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_upload_snapshot(user3: UserClientFlask): + uri = '/inventory/upload-snapshot/' + file_name = 'real-eee-1001pxd.snapshot.12.json' + body, status = user3.get(uri) + + assert status == '200 OK' + assert "Select a Snapshot file" in body + + snapshot = conftest.yaml2json(file_name.split(".json")[0]) + b_snapshot = bytes(json.dumps(snapshot), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + body, status = user3.post(uri, data=data, content_type="multipart/form-data") + + txt = f"{file_name}: Ok" + assert status == '200 OK' + assert txt in body + db_snapthot = Snapshot.query.one() + dev = db_snapthot.device + assert str(db_snapthot.uuid) == snapshot['uuid'] + assert dev.type == 'Laptop' + assert dev.serial_number == 'b8oaas048285' + assert len(dev.actions) == 12 + assert len(dev.components) == 9 + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_inventory_with_device(user3: UserClientFlask): + db_snapthot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + body, status = user3.get('/inventory/device/') + + assert status == '200 OK' + assert "Unassgined" in body + assert db_snapthot.device.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_inventory_filter(user3: UserClientFlask): + db_snapthot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + + csrf = generate_csrf() + body, status = user3.get(f'/inventory/device/?filter=Laptop&csrf_token={csrf}') + + assert status == '200 OK' + assert "Unassgined" in body + assert db_snapthot.device.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_export_devices(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + uri = "/inventory/export/devices/?ids={id}".format(id=snap.device.devicehub_id) + + body, status = user3.get(uri) + assert status == '200 OK' + + export_csv = [line.split(";") for line in body.split("\n")] + + with Path(__file__).parent.joinpath('files').joinpath( + 'export_devices.csv' + ).open() as csv_file: + obj_csv = csv.reader(csv_file, delimiter=';', quotechar='"') + fixture_csv = list(obj_csv) + + assert fixture_csv[0] == export_csv[0], 'Headers are not equal' + assert ( + fixture_csv[1][:19] == export_csv[1][:19] + ), 'Computer information are not equal' + assert fixture_csv[1][20] == export_csv[1][20], 'Computer information are not equal' + assert ( + fixture_csv[1][22:82] == export_csv[1][22:82] + ), 'Computer information are not equal' + assert fixture_csv[1][83] == export_csv[1][83], 'Computer information are not equal' + assert ( + fixture_csv[1][86:] == export_csv[1][86:] + ), 'Computer information are not equal' + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_export_metrics(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + uri = "/inventory/export/metrics/?ids={id}".format(id=snap.device.devicehub_id) + + body, status = user3.get(uri) + assert status == '200 OK' + assert body == '' + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_export_links(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + uri = "/inventory/export/links/?ids={id}".format(id=snap.device.devicehub_id) + + body, status = user3.get(uri) + assert status == '200 OK' + body = body.split("\n") + assert ['links', 'http://localhost/devices/O48N2', ''] == body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_export_certificates(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + uri = "/inventory/export/certificates/?ids={id}".format(id=snap.device.devicehub_id) + + body, status = user3.get(uri, decode=False) + body = str(next(body)) + assert status == '200 OK' + assert "PDF-1.5" in body + assert 'hts54322' in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_labels(user3: UserClientFlask): + body, status = user3.get('/labels/') + + assert status == '200 OK' + assert "Unique Identifiers Management" in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_add_tag(user3: UserClientFlask): + uri = '/labels/add/' + body, status = user3.get(uri) + + assert status == '200 OK' + assert "Add a new Unique Identifier" in body + + data = { + 'code': "tag1", + 'csrf_token': generate_csrf(), + } + body, status = user3.post(uri, data=data) + + assert status == '200 OK' + assert "tag1" in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_label_details(user3: UserClientFlask): + uri = '/labels/add/' + user3.get(uri) + + data = { + 'code': "tag1", + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data) + + body, status = user3.get('/labels/tag1/') + assert "tag1" in body + assert "Print Label" in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_link_tag_to_device(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/labels/add/' + user3.get(uri) + + data = { + 'code': "tag1", + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data) + + body, status = user3.get('/inventory/device/') + assert "tag1" in body + + data = { + 'tag': "tag1", + 'device': dev.id, + 'csrf_token': generate_csrf(), + } + + uri = '/inventory/tag/devices/add/' + user3.post(uri, data=data) + assert len(list(dev.tags)) == 2 + tags = [tag.id for tag in dev.tags] + assert "tag1" in tags + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_unlink_tag_to_device(user3: UserClientFlask): + # create device + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + + # create tag + uri = '/labels/add/' + user3.get(uri) + + data = { + 'code': "tag1", + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data) + + # link tag to device + data = { + 'tag': "tag1", + 'device': dev.id, + 'csrf_token': generate_csrf(), + } + + uri = '/inventory/tag/devices/add/' + user3.post(uri, data=data) + + # unlink tag to device + uri = '/inventory/tag/devices/{id}/del/'.format(id=dev.id) + user3.get(uri) + + data = { + 'code': "tag1", + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data) + + data = { + 'tag': "tag1", + 'csrf_token': generate_csrf(), + } + + user3.post(uri, data=data) + assert len(list(dev.tags)) == 1 + tag = list(dev.tags)[0] + assert not tag.id == "tag1" + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_print_labels(user3: UserClientFlask): + # create device + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + + # create tag + uri = '/labels/add/' + user3.get(uri) + + data = { + 'code': "tag1", + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data) + + # link tag to device + data = { + 'tag': "tag1", + 'device': dev.id, + 'csrf_token': generate_csrf(), + } + + uri = '/inventory/tag/devices/add/' + user3.post(uri, data=data) + + assert len(list(dev.tags)) == 2 + + uri = '/labels/print' + data = { + 'devices': "{}".format(dev.id), + 'csrf_token': generate_csrf(), + } + body, status = user3.post(uri, data=data) + + assert status == '200 OK' + path = "/inventory/device/{}/".format(dev.devicehub_id) + assert path in body + assert "tag1" not in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_add_monitor(user3: UserClientFlask): + uri = '/inventory/device/add/' + body, status = user3.get(uri) + assert status == '200 OK' + assert "New Device" in body + + data = { + 'csrf_token': generate_csrf(), + 'type': "Monitor", + 'serial_number': "AAAAB", + 'model': "LC27T55", + 'manufacturer': "Samsung", + 'generation': 1, + 'weight': 0.1, + 'height': 0.1, + 'depth': 0.1, + } + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert 'Device "Monitor" created successfully!' in body + dev = Device.query.one() + assert dev.type == 'Monitor' + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_filter_monitor(user3: UserClientFlask): + uri = '/inventory/device/add/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Monitor", + 'serial_number': "AAAAB", + 'model': "LC27T55", + 'manufacturer': "Samsung", + 'generation': 1, + 'weight': 0.1, + 'height': 0.1, + 'depth': 0.1, + } + user3.post(uri, data=data) + csrf = generate_csrf() + + uri = f'/inventory/device/?filter=Monitor&csrf_token={csrf}' + body, status = user3.get(uri) + + assert status == '200 OK' + dev = Device.query.one() + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_recycling(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + # fail request + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert dev.actions[-1].type == 'EreusePrice' + assert 'Action Allocate error!' in body + + # good request + data = { + 'csrf_token': generate_csrf(), + 'type': "Recycling", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Recycling' + assert 'Action "Recycling" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_error_without_devices(user3: UserClientFlask): + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Recycling", + 'severity': "Info", + 'devices': "", + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert 'Action Recycling error!' in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_use(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Use", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Use' + assert 'Action "Use" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_refurbish(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Refurbish", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Refurbish' + assert 'Action "Refurbish" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_management(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Management", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Management' + assert 'Action "Management" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_allocate(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-01', + 'end_time': '2000-06-01', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Allocate' + assert 'Action "Allocate" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_allocate_error_required(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Trade", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/allocate/add/' + body, status = user3.post(uri, data=data) + assert dev.actions[-1].type != 'Allocate' + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/allocate/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert 'Action Allocate error' in body + assert 'You need to specify a number of users!' in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_allocate_error_dates(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-06-01', + 'end_time': '2000-01-01', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert 'Action Allocate error' in body + assert 'The action cannot finish before it starts.' in body + assert dev.actions[-1].type != 'Allocate' + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_deallocate(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-01', + 'end_time': '2000-06-01', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + + user3.post(uri, data=data) + assert dev.actions[-1].type == 'Allocate' + + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-01', + 'end_time': '2000-06-01', + 'end_users': 2, + } + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Deallocate' + assert 'Action "Deallocate" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_toprepare(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "ToPrepare", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'ToPrepare' + assert 'Action "ToPrepare" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_prepare(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Prepare", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Prepare' + assert 'Action "Prepare" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_torepair(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "ToRepair", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'ToRepair' + assert 'Action "ToRepair" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_ready(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Ready", + 'severity': "Info", + 'devices': "{}".format(dev.id), + } + + uri = '/inventory/action/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.actions[-1].type == 'Ready' + assert 'Action "Ready" created successfully!' in body + assert dev.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_datawipe(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + b_file = b'1234567890' + file_name = "my_file.doc" + file_upload = (BytesIO(b_file), file_name) + + data = { + 'csrf_token': generate_csrf(), + 'type': "DataWipe", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'document-file_name': file_upload, + } + + uri = '/inventory/action/datawipe/add/' + body, status = user3.post(uri, data=data, content_type="multipart/form-data") + assert status == '200 OK' + assert dev.actions[-1].type == 'DataWipe' + assert 'Action "DataWipe" created successfully!' in body + assert dev.devicehub_id in body