From 0b1f856ce6b8a69d9df86fd4f2033041baed0d2b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 11 Mar 2022 19:16:39 +0100 Subject: [PATCH 01/18] modify Changelog.md --- CHANGELOG.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83520944..96d64604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.ht ml). -## master - [1.0.12-beta] -## testing - [1.0.13-beta] - -## [1.0.13-beta] +## [2.0.0-alpha] +- [changes] #209 adding a new device in a lot if it is created from a lot +- [addend] #208 render from backend filter for type of devices in the general list +- [bugfix] #206 fix 2 bugs about visibility devices when you are not the owner +- [addend] #205 ux improvements +- [addend] #204 render from backend export files +- [addend] #203 render from backend Trade action +- [addend] #201 render from backend Data Wipe action +- [addend] #196 render from backend action system +- [addend] #195 render from backend tags system +- [addend] #193 render from backend devices and lots +- [changes] #191 pass to drop teal and use the pure flask and use render from flask ## [1.0.12-beta] - [changes] #187 now is possible duplicate slots of RAM. From 4e35660080ccb8fc6351bc7945b3a19ba4bccbfc Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 14 Mar 2022 13:32:39 +0100 Subject: [PATCH 02/18] change conftest --- tests/conftest.py | 61 +++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 991374ba..b85b34fd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,15 @@ 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 @@ -17,11 +17,12 @@ from ereuse_devicehub.client import Client, UserClient 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.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 +51,19 @@ 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.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 +75,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 +115,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,7 +129,9 @@ 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 @@ -145,16 +165,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 +185,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) From 071e1452645fe8b7e28c213b6ceb083daea9fedc Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Mar 2022 11:17:05 +0100 Subject: [PATCH 03/18] Add body in the check of request --- tests/test_render_2_0.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_render_2_0.py diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py new file mode 100644 index 00000000..45c79d30 --- /dev/null +++ b/tests/test_render_2_0.py @@ -0,0 +1,28 @@ +# import pytest +from flask.testing import FlaskClient + +from ereuse_devicehub.client import UserClient +from ereuse_devicehub.devicehub import Devicehub + +# from tests import conftest + + +# @pytest.mark.mvp +# @pytest.mark.usefixtures() +# def test_create_application(client: FlaskClient, mocker): +# @pytest.mark.usefixtures(conftest.app_context.__name__) +def test_login(user: UserClient, app: Devicehub): + """Checks a simple login""" + + client = FlaskClient(app, use_cookies=True, response_wrapper=app.response_class) + 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} + body, status, headers = client.post('/login/', data=data) + + body = next(body).decode("utf-8") + assert status == '200 OK' + assert "Login to Your Account" not in body From 39dc4d86354793853924915276f41d0f0fe42229 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 15 Mar 2022 19:57:54 +0100 Subject: [PATCH 04/18] add one example that how use client validate --- tests/test_render_2_0.py | 43 +++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 45c79d30..0a3a19ad 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -1,28 +1,53 @@ -# import pytest +import pytest from flask.testing import FlaskClient +from flask_wtf.csrf import generate_csrf from ereuse_devicehub.client import UserClient from ereuse_devicehub.devicehub import Devicehub - -# from tests import conftest +from tests import conftest -# @pytest.mark.mvp +@pytest.mark.mvp # @pytest.mark.usefixtures() -# def test_create_application(client: FlaskClient, mocker): -# @pytest.mark.usefixtures(conftest.app_context.__name__) +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_login(user: UserClient, app: Devicehub): """Checks a simple login""" - client = FlaskClient(app, use_cookies=True, response_wrapper=app.response_class) + 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} - body, status, headers = client.post('/login/', data=data) + 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_inventory(user: UserClient, app: Devicehub): + client = FlaskClient(app, use_cookies=True) + client.get('/login/') + data = { + 'email': user.email, + 'password': 'foo', + 'remember': False, + 'csrf_token': generate_csrf(), + } + body, status, headers = client.post('/login/', data=data, follow_redirects=True) + body, status, headers = client.get('/inventory/device/', headers=headers) + + body = next(body).decode("utf-8") + assert status == '200 OK' + # import pdb; pdb.set_trace() + assert "Unassgined" in body From ec99ad22b5957ab6af3f0938864b088ac8c75d9d Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 17 Mar 2022 11:50:57 +0100 Subject: [PATCH 05/18] . --- ereuse_devicehub/client.py | 60 ++++++++++++++++++++++++++++++++++++++ tests/conftest.py | 27 ++++++++++++++++- tests/test_render_2_0.py | 34 ++++++++++++--------- 3 files changed, 107 insertions(+), 14 deletions(-) diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py index 3224dfd3..3ddf94ff 100644 --- a/ereuse_devicehub/client.py +++ b/ereuse_devicehub/client.py @@ -1,5 +1,7 @@ from inspect import isclass from typing import Dict, Iterable, Type, Union +from flask.testing import FlaskClient +from flask_wtf.csrf import generate_csrf from ereuse_utils.test import JSON, Res from teal.client import Client as TealClient, Query, Status @@ -156,3 +158,61 @@ class UserClient(Client): 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, + **kw): + + body, status, headers = self.client.get( + uri, + data=data, + follow_redirects=follow_redirects, + headers=self.headers + ) + body = next(body).decode("utf-8") + return (body, status) + + def post(self, + uri='', + data=None, + follow_redirects=True, + **kw): + + import pdb; pdb.set_trace() + body, status, headers = self.client.post( + uri, + data=data, + follow_redirects=follow_redirects, + headers=self.headers + ) + body = next(body).decode("utf-8") + return (body, status) diff --git a/tests/conftest.py b/tests/conftest.py index b85b34fd..e571e96b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ 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 @@ -136,6 +136,31 @@ def user2(app: Devicehub) -> UserClient: 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')) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 0a3a19ad..3d3775ed 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -2,7 +2,7 @@ import pytest from flask.testing import FlaskClient from flask_wtf.csrf import generate_csrf -from ereuse_devicehub.client import UserClient +from ereuse_devicehub.client import UserClient, UserClientFlask from ereuse_devicehub.devicehub import Devicehub from tests import conftest @@ -35,19 +35,27 @@ def test_login(user: UserClient, app: Devicehub): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_inventory(user: UserClient, app: Devicehub): - client = FlaskClient(app, use_cookies=True) - client.get('/login/') +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/') + + assert status == '200 OK' + assert "Add a new lot" in body + data = { - 'email': user.email, - 'password': 'foo', - 'remember': False, + 'name': 'lot1', 'csrf_token': generate_csrf(), } - body, status, headers = client.post('/login/', data=data, follow_redirects=True) - body, status, headers = client.get('/inventory/device/', headers=headers) - - body = next(body).decode("utf-8") - assert status == '200 OK' # import pdb; pdb.set_trace() - assert "Unassgined" in body + body, status = user3.post('/inventory/lot/add/', data=data) + + assert status == '200 OK' + assert "lot1" in body From f2ab02804b366b93b0ed4aed1210a9a509f51ef0 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 17 Mar 2022 13:13:15 +0100 Subject: [PATCH 06/18] new userclient from flask instead of teal --- ereuse_devicehub/client.py | 329 +++++++++++++++++++++---------------- tests/conftest.py | 8 +- 2 files changed, 193 insertions(+), 144 deletions(-) diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py index 3ddf94ff..36d3bd49 100644 --- a/ereuse_devicehub/client.py +++ b/ereuse_devicehub/client.py @@ -1,10 +1,11 @@ from inspect import isclass from typing import Dict, Iterable, Type, Union -from flask.testing import FlaskClient -from flask_wtf.csrf import generate_csrf 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 @@ -15,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 ) @@ -128,30 +175,47 @@ 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): @@ -161,13 +225,15 @@ class UserClient(Client): class UserClientFlask: - - def __init__(self, application, - email: str, - password: str, - response_wrapper=None, - use_cookies=True, - follow_redirects=True): + 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 @@ -181,38 +247,25 @@ class UserClientFlask: 'password': password, 'csrf_token': generate_csrf(), } - body, status, headers = self.client.post('/login/', data=data, follow_redirects=True) + 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, - **kw): + def get(self, uri='', data=None, follow_redirects=True, **kw): body, status, headers = self.client.get( - uri, - data=data, - follow_redirects=follow_redirects, - headers=self.headers + uri, data=data, follow_redirects=follow_redirects, headers=self.headers ) body = next(body).decode("utf-8") return (body, status) - def post(self, - uri='', - data=None, - follow_redirects=True, - **kw): + def post(self, uri='', data=None, follow_redirects=True, **kw): - import pdb; pdb.set_trace() body, status, headers = self.client.post( - uri, - data=data, - follow_redirects=follow_redirects, - headers=self.headers + uri, data=data, follow_redirects=follow_redirects, headers=self.headers ) body = next(body).decode("utf-8") return (body, status) diff --git a/tests/conftest.py b/tests/conftest.py index e571e96b..1bd1ec71 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -142,9 +142,7 @@ def user3(app: Devicehub) -> UserClientFlask: with app.app_context(): password = 'foo' user = create_user(password=password) - client = UserClientFlask( - app, user.email, password - ) + client = UserClientFlask(app, user.email, password) return client @@ -155,9 +153,7 @@ def user4(app: Devicehub) -> UserClient: password = 'foo' email = 'foo2@foo.com' user = create_user(email=email, password=password) - client = UserClientFlask( - app, user.email, password - ) + client = UserClientFlask(app, user.email, password) return client From 25dc0047dc3a0ec7989554d7d0cebf8249592861 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 19 Apr 2022 18:39:05 +0200 Subject: [PATCH 07/18] add tests --- tests/conftest.py | 2 + tests/files/export_devices.csv | 2 + tests/test_render_2_0.py | 191 ++++++++++++++++++++++++++++++++- 3 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 tests/files/export_devices.csv diff --git a/tests/conftest.py b/tests/conftest.py index 1bd1ec71..a8d04506 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,7 @@ 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.enums import SessionType from ereuse_devicehub.resources.tag import Tag @@ -56,6 +57,7 @@ def _app(config: TestConfig) -> Devicehub: 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]) 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_render_2_0.py b/tests/test_render_2_0.py index 3d3775ed..7ebd5695 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -1,14 +1,20 @@ +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.lot.models import Lot from tests import conftest @pytest.mark.mvp -# @pytest.mark.usefixtures() @pytest.mark.usefixtures(conftest.app_context.__name__) def test_login(user: UserClient, app: Devicehub): """Checks a simple login""" @@ -47,15 +53,192 @@ def test_inventory(user3: UserClientFlask): 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': 'lot1', + 'name': lot_name, 'csrf_token': generate_csrf(), } - # import pdb; pdb.set_trace() body, status = user3.post('/inventory/lot/add/', data=data) assert status == '200 OK' - assert "lot1" in body + 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 + # import pdb; pdb.set_trace() + 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): + uri = '/inventory/upload-snapshot/' + file_name = 'real-eee-1001pxd.snapshot.12.json' + snapshot = conftest.yaml2json(file_name.split(".json")[0]) + b_snapshot = bytes(json.dumps(snapshot), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + user3.get(uri) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data, content_type="multipart/form-data") + + body, status = user3.get('/inventory/device/') + + assert status == '200 OK' + assert "Unassgined" in body + db_snapthot = Snapshot.query.one() + assert db_snapthot.device.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_inventory_filter(user3: UserClientFlask): + uri = '/inventory/upload-snapshot/' + file_name = 'real-eee-1001pxd.snapshot.12.json' + snapshot = conftest.yaml2json(file_name.split(".json")[0]) + b_snapshot = bytes(json.dumps(snapshot), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + user3.get(uri) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data, content_type="multipart/form-data") + + csrf = generate_csrf() + body, status = user3.get(f'/inventory/device/?filter=Laptop&csrf_token={csrf}') + + assert status == '200 OK' + assert "Unassgined" in body + db_snapthot = Snapshot.query.one() + assert db_snapthot.device.devicehub_id in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_export_devices(user3: UserClientFlask): + uri = '/inventory/upload-snapshot/' + file_name = 'real-eee-1001pxd.snapshot.12.json' + snapshot = conftest.yaml2json(file_name.split(".json")[0]) + b_snapshot = bytes(json.dumps(snapshot), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + user3.get(uri) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data, content_type="multipart/form-data") + + snap = Snapshot.query.one() + 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' From 4a5ad374f8f2e82374219f929cad3e95dc8d02e4 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 19 Apr 2022 18:39:42 +0200 Subject: [PATCH 08/18] fix UserClientFlask --- ereuse_devicehub/client.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py index 36d3bd49..31a9f136 100644 --- a/ereuse_devicehub/client.py +++ b/ereuse_devicehub/client.py @@ -254,7 +254,14 @@ class UserClientFlask: body = next(body).decode("utf-8") assert "Unassgined" in body - def get(self, uri='', data=None, follow_redirects=True, **kw): + def get( + self, + uri='', + data=None, + follow_redirects=True, + content_type='text/html; charset=utf-8', + **kw, + ): body, status, headers = self.client.get( uri, data=data, follow_redirects=follow_redirects, headers=self.headers @@ -262,10 +269,21 @@ class UserClientFlask: body = next(body).decode("utf-8") return (body, status) - def post(self, uri='', data=None, follow_redirects=True, **kw): + def post( + self, + uri='', + data=None, + follow_redirects=True, + content_type='application/x-www-form-urlencoded', + **kw, + ): body, status, headers = self.client.post( - uri, data=data, follow_redirects=follow_redirects, headers=self.headers + uri, + data=data, + follow_redirects=follow_redirects, + headers=self.headers, + content_type=content_type, ) body = next(body).decode("utf-8") return (body, status) From 7c93fc68c5e1a5ce73a1e9e45ef148506408b95f Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 20 Apr 2022 12:35:25 +0200 Subject: [PATCH 09/18] fix encode of response in client --- ereuse_devicehub/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py index 31a9f136..aaafb9f3 100644 --- a/ereuse_devicehub/client.py +++ b/ereuse_devicehub/client.py @@ -260,13 +260,15 @@ class UserClientFlask: 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 ) - body = next(body).decode("utf-8") + if decode: + body = next(body).decode("utf-8") return (body, status) def post( @@ -275,6 +277,7 @@ class UserClientFlask: data=None, follow_redirects=True, content_type='application/x-www-form-urlencoded', + decode=True, **kw, ): @@ -285,5 +288,6 @@ class UserClientFlask: headers=self.headers, content_type=content_type, ) - body = next(body).decode("utf-8") + if decode: + body = next(body).decode("utf-8") return (body, status) From 2fecd1aa60f8d67a511765b3ced61da0c763dcc6 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 20 Apr 2022 12:35:50 +0200 Subject: [PATCH 10/18] add more test --- tests/test_render_2_0.py | 261 +++++++++++++++++++++++++++++++++------ 1 file changed, 220 insertions(+), 41 deletions(-) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 7ebd5695..0b9dc184 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -14,6 +14,22 @@ 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): @@ -154,69 +170,31 @@ def test_upload_snapshot(user3: UserClientFlask): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_inventory_with_device(user3: UserClientFlask): - uri = '/inventory/upload-snapshot/' - file_name = 'real-eee-1001pxd.snapshot.12.json' - snapshot = conftest.yaml2json(file_name.split(".json")[0]) - b_snapshot = bytes(json.dumps(snapshot), 'utf-8') - file_snap = (BytesIO(b_snapshot), file_name) - user3.get(uri) - - data = { - 'snapshot': file_snap, - 'csrf_token': generate_csrf(), - } - user3.post(uri, data=data, content_type="multipart/form-data") - + 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 - db_snapthot = Snapshot.query.one() assert db_snapthot.device.devicehub_id in body @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_inventory_filter(user3: UserClientFlask): - uri = '/inventory/upload-snapshot/' - file_name = 'real-eee-1001pxd.snapshot.12.json' - snapshot = conftest.yaml2json(file_name.split(".json")[0]) - b_snapshot = bytes(json.dumps(snapshot), 'utf-8') - file_snap = (BytesIO(b_snapshot), file_name) - user3.get(uri) - - data = { - 'snapshot': file_snap, - 'csrf_token': generate_csrf(), - } - user3.post(uri, data=data, content_type="multipart/form-data") + 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 - db_snapthot = Snapshot.query.one() assert db_snapthot.device.devicehub_id in body @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_export_devices(user3: UserClientFlask): - uri = '/inventory/upload-snapshot/' - file_name = 'real-eee-1001pxd.snapshot.12.json' - snapshot = conftest.yaml2json(file_name.split(".json")[0]) - b_snapshot = bytes(json.dumps(snapshot), 'utf-8') - file_snap = (BytesIO(b_snapshot), file_name) - user3.get(uri) - - data = { - 'snapshot': file_snap, - 'csrf_token': generate_csrf(), - } - user3.post(uri, data=data, content_type="multipart/form-data") - - snap = Snapshot.query.one() + 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) @@ -242,3 +220,204 @@ def test_export_devices(user3: UserClientFlask): 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 "Tags 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 Tag" 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 + tag = list(dev.tags)[0] + assert tag.id == "tag1" + + +@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 From b986b6e95a1e394f49f3a64541c455292a0c25de Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 20 Apr 2022 18:22:22 +0200 Subject: [PATCH 11/18] more tests --- tests/test_render_2_0.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 0b9dc184..a95b8fa5 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -10,6 +10,7 @@ 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 @@ -421,3 +422,58 @@ def test_print_labels(user3: UserClientFlask): 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() + # import pdb; pdb.set_trace() + + 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 From fd74804f35edf02384879a74f9da19696aa85bce Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 21 Apr 2022 14:01:03 +0200 Subject: [PATCH 12/18] add actions test --- tests/test_render_2_0.py | 383 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 379 insertions(+), 4 deletions(-) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index a95b8fa5..0565d153 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -56,6 +56,16 @@ def test_login(user: UserClient, app: Devicehub): 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): @@ -116,7 +126,6 @@ def test_update_lot(user3: UserClientFlask): user3.get('/inventory/lot/add/') # Add lot - # import pdb; pdb.set_trace() data = { 'name': "lot1", 'csrf_token': generate_csrf(), @@ -330,8 +339,8 @@ def test_link_tag_to_device(user3: UserClientFlask): uri = '/inventory/tag/devices/add/' user3.post(uri, data=data) assert len(list(dev.tags)) == 2 - tag = list(dev.tags)[0] - assert tag.id == "tag1" + tags = [tag.id for tag in dev.tags] + assert "tag1" in tags @pytest.mark.mvp @@ -469,7 +478,6 @@ def test_filter_monitor(user3: UserClientFlask): } user3.post(uri, data=data) csrf = generate_csrf() - # import pdb; pdb.set_trace() uri = f'/inventory/device/?filter=Monitor&csrf_token={csrf}' body, status = user3.get(uri) @@ -477,3 +485,370 @@ def test_filter_monitor(user3: UserClientFlask): 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 '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 '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 From e37fa49c3e2bea9bc3bde04b6534a610697c1c23 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 21 Apr 2022 14:01:33 +0200 Subject: [PATCH 13/18] fix bug in validation actions --- ereuse_devicehub/inventory/forms.py | 53 +++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 14 deletions(-) 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 From f9be7f0a14eaaef90e4f57958b501d4c363eda7b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 21 Apr 2022 14:02:16 +0200 Subject: [PATCH 14/18] return error response in post request --- ereuse_devicehub/inventory/views.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 38ff4bc6..e6110f97 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -315,16 +315,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 @@ -350,10 +353,9 @@ 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)) + next_url = self.get_next_url() + return flask.redirect(next_url) class NewDataWipeView(NewActionView, DeviceListMix): @@ -372,10 +374,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): @@ -394,10 +395,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): From ad0ced98e42a98baa1624be41ded5e042209f08d Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 29 Apr 2022 13:10:44 +0200 Subject: [PATCH 15/18] refactor common context --- ereuse_devicehub/inventory/views.py | 162 +++++++++++++--------------- ereuse_devicehub/views.py | 46 ++++++-- 2 files changed, 113 insertions(+), 95 deletions(-) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 38ff4bc6..056b99b7 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -7,10 +7,9 @@ import flask_weasyprint from flask import Blueprint, g, make_response, request, url_for from flask.views import View from flask_login import current_user, login_required -from sqlalchemy import or_ from werkzeug.exceptions import NotFound -from ereuse_devicehub import __version__, messages +from ereuse_devicehub import messages from ereuse_devicehub.db import db from ereuse_devicehub.inventory.forms import ( AllocateForm, @@ -31,35 +30,21 @@ from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow from ereuse_devicehub.resources.hash_reports import insert_hash from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.tag.model import Tag +from ereuse_devicehub.views import GenericMixView devices = Blueprint('inventory', __name__, url_prefix='/inventory') logger = logging.getLogger(__name__) -class GenericMixView(View): - def get_lots(self): - return ( - Lot.query.outerjoin(Trade) - .filter( - or_( - Trade.user_from == g.user, - Trade.user_to == g.user, - Lot.owner_id == g.user.id, - ) - ) - .distinct() - ) - - class DeviceListMix(GenericMixView): - decorators = [login_required] template_name = 'inventory/device_list.html' def get_context(self, lot_id): + super().get_context() + lots = self.context['lots'] form_filter = FilterForm() filter_types = form_filter.search() - lots = self.get_lots() lot = None tags = ( Tag.query.filter(Tag.owner_id == current_user.id) @@ -105,21 +90,21 @@ class DeviceListMix(GenericMixView): if action_devices: list_devices.extend([int(x) for x in action_devices.split(",")]) - self.context = { - 'devices': devices, - 'lots': lots, - 'form_tag_device': TagDeviceForm(), - 'form_new_action': form_new_action, - 'form_new_allocate': form_new_allocate, - 'form_new_datawipe': form_new_datawipe, - 'form_new_trade': form_new_trade, - 'form_filter': form_filter, - 'form_print_labels': PrintLabelsForm(), - 'lot': lot, - 'tags': tags, - 'list_devices': list_devices, - 'version': __version__, - } + self.context.update( + { + 'devices': devices, + 'form_tag_device': TagDeviceForm(), + 'form_new_action': form_new_action, + 'form_new_allocate': form_new_allocate, + 'form_new_datawipe': form_new_datawipe, + 'form_new_trade': form_new_trade, + 'form_filter': form_filter, + 'form_print_labels': PrintLabelsForm(), + 'lot': lot, + 'tags': tags, + 'list_devices': list_devices, + } + ) return self.context @@ -135,20 +120,20 @@ class DeviceDetailView(GenericMixView): template_name = 'inventory/device_detail.html' def dispatch_request(self, id): - lots = self.get_lots() + self.get_context() device = ( Device.query.filter(Device.owner_id == current_user.id) .filter(Device.devicehub_id == id) .one() ) - context = { - 'device': device, - 'lots': lots, - 'page_title': 'Device {}'.format(device.devicehub_id), - 'version': __version__, - } - return flask.render_template(self.template_name, **context) + self.context.update( + { + 'device': device, + 'page_title': 'Device {}'.format(device.devicehub_id), + } + ) + return flask.render_template(self.template_name, **self.context) class LotCreateView(GenericMixView): @@ -164,14 +149,14 @@ class LotCreateView(GenericMixView): next_url = url_for('inventory.lotdevicelist', lot_id=form.id) return flask.redirect(next_url) - lots = self.get_lots() - context = { - 'form': form, - 'title': self.title, - 'lots': lots, - 'version': __version__, - } - return flask.render_template(self.template_name, **context) + self.get_context() + self.context.update( + { + 'form': form, + 'title': self.title, + } + ) + return flask.render_template(self.template_name, **self.context) class LotUpdateView(View): @@ -187,14 +172,14 @@ class LotUpdateView(View): next_url = url_for('inventory.lotdevicelist', lot_id=id) return flask.redirect(next_url) - lots = Lot.query.filter(Lot.owner_id == current_user.id) - context = { - 'form': form, - 'title': self.title, - 'lots': lots, - 'version': __version__, - } - return flask.render_template(self.template_name, **context) + self.get_context() + self.context.update( + { + 'form': form, + 'title': self.title, + } + ) + return flask.render_template(self.template_name, **self.context) class LotDeleteView(View): @@ -221,24 +206,25 @@ class UploadSnapshotView(GenericMixView): template_name = 'inventory/upload_snapshot.html' def dispatch_request(self, lot_id=None): - lots = self.get_lots() + self.get_context() form = UploadSnapshotForm() - context = { - 'page_title': 'Upload Snapshot', - 'lots': lots, - 'form': form, - 'lot_id': lot_id, - 'version': __version__, - } + self.context.update( + { + 'page_title': 'Upload Snapshot', + 'form': form, + 'lot_id': lot_id, + } + ) if form.validate_on_submit(): snapshot = form.save(commit=False) if lot_id: + lots = self.context['lots'] lot = lots.filter(Lot.id == lot_id).one() lot.devices.add(snapshot.device) db.session.add(lot) db.session.commit() - return flask.render_template(self.template_name, **context) + return flask.render_template(self.template_name, **self.context) class DeviceCreateView(GenericMixView): @@ -247,20 +233,21 @@ class DeviceCreateView(GenericMixView): template_name = 'inventory/device_create.html' def dispatch_request(self, lot_id=None): - lots = self.get_lots() + self.get_context() form = NewDeviceForm() - context = { - 'page_title': 'New Device', - 'lots': lots, - 'form': form, - 'lot_id': lot_id, - 'version': __version__, - } + self.context.update( + { + 'page_title': 'New Device', + 'form': form, + 'lot_id': lot_id, + } + ) if form.validate_on_submit(): snapshot = form.save(commit=False) next_url = url_for('inventory.devicelist') if lot_id: next_url = url_for('inventory.lotdevicelist', lot_id=lot_id) + lots = self.context['lots'] lot = lots.filter(Lot.id == lot_id).one() lot.devices.add(snapshot.device) db.session.add(lot) @@ -269,7 +256,7 @@ class DeviceCreateView(GenericMixView): messages.success('Device "{}" created successfully!'.format(form.type.data)) return flask.redirect(next_url) - return flask.render_template(self.template_name, **context) + return flask.render_template(self.template_name, **self.context) class TagLinkDeviceView(View): @@ -285,13 +272,13 @@ class TagLinkDeviceView(View): return flask.redirect(request.referrer) -class TagUnlinkDeviceView(View): +class TagUnlinkDeviceView(GenericMixView): methods = ['POST', 'GET'] decorators = [login_required] template_name = 'inventory/tag_unlink_device.html' def dispatch_request(self, id): - lots = Lot.query.filter(Lot.owner_id == current_user.id) + self.get_context() form = TagDeviceForm(delete=True, device=id) if form.validate_on_submit(): form.remove() @@ -299,14 +286,15 @@ class TagUnlinkDeviceView(View): next_url = url_for('inventory.devicelist') return flask.redirect(next_url) - return flask.render_template( - self.template_name, - form=form, - lots=lots, - referrer=request.referrer, - version=__version__, + self.context.update( + { + 'form': form, + 'referrer': request.referrer, + } ) + return flask.render_template(self.template_name, **self.context) + class NewActionView(View): methods = ['POST'] @@ -409,6 +397,7 @@ class NewTradeDocumentView(View): def dispatch_request(self, lot_id): self.form = self.form_class(lot=lot_id) + self.get_context() if self.form.validate_on_submit(): self.form.save() @@ -416,9 +405,8 @@ class NewTradeDocumentView(View): next_url = url_for('inventory.lotdevicelist', lot_id=lot_id) return flask.redirect(next_url) - return flask.render_template( - self.template_name, form=self.form, title=self.title, version=__version__ - ) + self.context.update({'form': self.form, 'title': self.title}) + return flask.render_template(self.template_name, **self.context) class ExportsView(View): diff --git a/ereuse_devicehub/views.py b/ereuse_devicehub/views.py index 1c975285..bee12ad0 100644 --- a/ereuse_devicehub/views.py +++ b/ereuse_devicehub/views.py @@ -1,11 +1,14 @@ import flask -from flask import Blueprint +from flask import Blueprint, g from flask.views import View from flask_login import current_user, login_required, login_user, logout_user +from sqlalchemy import or_ from ereuse_devicehub import __version__, messages from ereuse_devicehub.db import db from ereuse_devicehub.forms import LoginForm, PasswordForm +from ereuse_devicehub.resources.action.models import Trade +from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.utils import is_safe_url @@ -46,18 +49,45 @@ class LogoutView(View): return flask.redirect(flask.url_for('core.login')) -class UserProfileView(View): +class GenericMixView(View): + decorators = [login_required] + + def get_lots(self): + return ( + Lot.query.outerjoin(Trade) + .filter( + or_( + Trade.user_from == g.user, + Trade.user_to == g.user, + Lot.owner_id == g.user.id, + ) + ) + .distinct() + ) + + def get_context(self): + self.context = { + 'lots': self.get_lots(), + 'version': __version__, + } + + return self.context + + +class UserProfileView(GenericMixView): decorators = [login_required] template_name = 'ereuse_devicehub/user_profile.html' def dispatch_request(self): - context = { - 'current_user': current_user, - 'version': __version__, - 'password_form': PasswordForm(), - } + self.get_context() + self.context.update( + { + 'current_user': current_user, + 'password_form': PasswordForm(), + } + ) - return flask.render_template(self.template_name, **context) + return flask.render_template(self.template_name, **self.context) class UserPasswordView(View): From 322b340607b8e4d56d4fad7299ed58886bf3b7b2 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 29 Apr 2022 17:41:13 +0200 Subject: [PATCH 16/18] add fix test with redirects --- tests/test_render_2_0.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 0565d153..16c950bf 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -274,7 +274,7 @@ def test_labels(user3: UserClientFlask): body, status = user3.get('/labels/') assert status == '200 OK' - assert "Tags Management" in body + assert "Unique Identifiers Management" in body @pytest.mark.mvp @@ -284,7 +284,7 @@ def test_add_tag(user3: UserClientFlask): body, status = user3.get(uri) assert status == '200 OK' - assert "Add a new Tag" in body + assert "Add a new Unique Identifier" in body data = { 'code': "tag1", @@ -667,7 +667,7 @@ def test_action_allocate_error_required(user3: UserClientFlask): uri = '/inventory/action/allocate/add/' body, status = user3.post(uri, data=data) assert status == '200 OK' - assert 'You need to specify a number of users' in body + assert 'Action Allocate error' in body @pytest.mark.mvp @@ -691,7 +691,7 @@ def test_action_allocate_error_dates(user3: UserClientFlask): uri = '/inventory/action/allocate/add/' body, status = user3.post(uri, data=data) assert status == '200 OK' - assert 'The action cannot finish before it starts.' in body + assert 'Action Allocate error' in body assert dev.actions[-1].type != 'Allocate' From 7fe33f3a42fa1e6a27431cd69c0c7f378b1b47f6 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 29 Apr 2022 17:53:59 +0200 Subject: [PATCH 17/18] ass form errors in message --- ereuse_devicehub/inventory/views.py | 3 +++ tests/test_render_2_0.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index e6110f97..2f47ff92 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -354,6 +354,9 @@ class NewAllocateView(NewActionView, DeviceListMix): return flask.redirect(next_url) 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) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 16c950bf..0df56652 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -668,6 +668,7 @@ def test_action_allocate_error_required(user3: UserClientFlask): 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 @@ -692,6 +693,7 @@ def test_action_allocate_error_dates(user3: UserClientFlask): 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' From 921049a28b62131f7485dca28c13fcdff76dfe06 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 29 Apr 2022 18:23:05 +0200 Subject: [PATCH 18/18] add more render basic tests --- tests/test_basic.py | 52 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 12 deletions(-) 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