Merge pull request #96 from eReuse/feature/#95-endpoint-check-hash-reports
Feature/#95 endpoint check hash reports
This commit is contained in:
commit
3f4484cb07
|
@ -6,13 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
ml).
|
ml).
|
||||||
|
|
||||||
## master
|
## master
|
||||||
[1.0.1-beta]
|
[1.0.2-beta]
|
||||||
|
|
||||||
## testing
|
## testing
|
||||||
[1.0.3-beta]
|
[1.0.3-beta]
|
||||||
|
|
||||||
## [1.0.3-beta]
|
## [1.0.3-beta]
|
||||||
- [addend] #85 add mac of network adapter to device hid
|
- [addend] #85 add mac of network adapter to device hid
|
||||||
|
- [addend] #95 adding endpoint for check the hash of one report
|
||||||
- [changed] #94 change form of snapshot manual
|
- [changed] #94 change form of snapshot manual
|
||||||
|
|
||||||
## [1.0.2-beta]
|
## [1.0.2-beta]
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 3eb50297c365
|
||||||
|
Revises: 378b6b147b46
|
||||||
|
Create Date: 2020-12-18 16:26:15.453694
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import citext
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from alembic import context
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3eb50297c365'
|
||||||
|
down_revision = '378b6b147b46'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_inv():
|
||||||
|
INV = context.get_x_argument(as_dictionary=True).get('inventory')
|
||||||
|
if not INV:
|
||||||
|
raise ValueError("Inventory value is not specified")
|
||||||
|
return INV
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Report Hash table
|
||||||
|
op.create_table('report_hash',
|
||||||
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.Column('created', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||||
|
nullable=False, comment='When Devicehub created this.'),
|
||||||
|
sa.Column('hash3', citext.CIText(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
schema=f'{get_inv()}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('report_hash', schema=f'{get_inv()}')
|
|
@ -11,19 +11,19 @@ import flask
|
||||||
import flask_weasyprint
|
import flask_weasyprint
|
||||||
import teal.marshmallow
|
import teal.marshmallow
|
||||||
from boltons import urlutils
|
from boltons import urlutils
|
||||||
from flask import make_response, g
|
from flask import make_response, g, request
|
||||||
|
from flask.json import jsonify
|
||||||
from teal.cache import cache
|
from teal.cache import cache
|
||||||
from teal.resource import Resource
|
from teal.resource import Resource, View
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.action import models as evs
|
from ereuse_devicehub.resources.action import models as evs
|
||||||
from ereuse_devicehub.resources.device import models as devs
|
from ereuse_devicehub.resources.device import models as devs
|
||||||
from ereuse_devicehub.resources.device.views import DeviceView
|
from ereuse_devicehub.resources.device.views import DeviceView
|
||||||
from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow
|
from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow
|
||||||
from ereuse_devicehub.resources.documents.device_row import DeviceRow
|
|
||||||
from ereuse_devicehub.resources.lot import LotView
|
from ereuse_devicehub.resources.lot import LotView
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
|
from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash
|
||||||
|
|
||||||
|
|
||||||
class Format(enum.Enum):
|
class Format(enum.Enum):
|
||||||
|
@ -117,7 +117,7 @@ class DevicesDocumentView(DeviceView):
|
||||||
def generate_post_csv(self, query):
|
def generate_post_csv(self, query):
|
||||||
"""Get device query and put information in csv format."""
|
"""Get device query and put information in csv format."""
|
||||||
data = StringIO()
|
data = StringIO()
|
||||||
cw = csv.writer(data, delimiter=';', quotechar='"')
|
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||||
first = True
|
first = True
|
||||||
for device in query:
|
for device in query:
|
||||||
d = DeviceRow(device)
|
d = DeviceRow(device)
|
||||||
|
@ -125,7 +125,9 @@ class DevicesDocumentView(DeviceView):
|
||||||
cw.writerow(d.keys())
|
cw.writerow(d.keys())
|
||||||
first = False
|
first = False
|
||||||
cw.writerow(d.values())
|
cw.writerow(d.values())
|
||||||
output = make_response(data.getvalue().encode('utf-8'))
|
bfile = data.getvalue().encode('utf-8')
|
||||||
|
output = make_response(bfile)
|
||||||
|
insert_hash(bfile)
|
||||||
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
|
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
|
||||||
output.headers['Content-type'] = 'text/csv'
|
output.headers['Content-type'] = 'text/csv'
|
||||||
return output
|
return output
|
||||||
|
@ -190,6 +192,19 @@ class StockDocumentView(DeviceView):
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
class CheckView(View):
|
||||||
|
model = ReportHash
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
qry = dict(request.values)
|
||||||
|
hash3 = qry.get('hash')
|
||||||
|
|
||||||
|
result = False
|
||||||
|
if hash3 and ReportHash.query.filter_by(hash3=hash3).count():
|
||||||
|
result = True
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
class DocumentDef(Resource):
|
class DocumentDef(Resource):
|
||||||
__type__ = 'Document'
|
__type__ = 'Document'
|
||||||
SCHEMA = None
|
SCHEMA = None
|
||||||
|
@ -238,3 +253,6 @@ class DocumentDef(Resource):
|
||||||
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth)
|
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth)
|
||||||
stock_view = app.auth.requires_auth(stock_view)
|
stock_view = app.auth.requires_auth(stock_view)
|
||||||
self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get)
|
self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get)
|
||||||
|
|
||||||
|
check_view = CheckView.as_view('CheckView', definition=self, auth=app.auth)
|
||||||
|
self.add_url_rule('/check/', defaults={}, view_func=check_view, methods=get)
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
"""Hash implementation and save in database
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from citext import CIText
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
|
||||||
|
|
||||||
|
class ReportHash(db.Model):
|
||||||
|
"""Save the hash than is create when one report is download.
|
||||||
|
"""
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
|
id.comment = """The identifier of the device for this database. Used only
|
||||||
|
internally for software; users should not use this.
|
||||||
|
"""
|
||||||
|
hash3 = db.Column(CIText(), nullable=False)
|
||||||
|
hash3.comment = """The normalized name of the hash."""
|
||||||
|
|
||||||
|
|
||||||
|
def insert_hash(bfile):
|
||||||
|
hash3 = hashlib.sha3_256(bfile).hexdigest()
|
||||||
|
db_hash = ReportHash(hash3=hash3)
|
||||||
|
db.session.add(db_hash)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.flush()
|
|
@ -53,6 +53,7 @@ def test_api_docs(client: Client):
|
||||||
'/documents/lots/',
|
'/documents/lots/',
|
||||||
'/documents/static/{filename}',
|
'/documents/static/{filename}',
|
||||||
'/documents/stock/',
|
'/documents/stock/',
|
||||||
|
'/documents/check/',
|
||||||
'/drills/{dev1_id}/merge/{dev2_id}',
|
'/drills/{dev1_id}/merge/{dev2_id}',
|
||||||
'/graphic-cards/{dev1_id}/merge/{dev2_id}',
|
'/graphic-cards/{dev1_id}/merge/{dev2_id}',
|
||||||
'/hard-drives/{dev1_id}/merge/{dev2_id}',
|
'/hard-drives/{dev1_id}/merge/{dev2_id}',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import csv
|
import csv
|
||||||
|
import hashlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -15,7 +16,9 @@ from ereuse_devicehub.resources.documents import documents
|
||||||
from ereuse_devicehub.resources.device import models as d
|
from ereuse_devicehub.resources.device import models as d
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
from ereuse_devicehub.resources.hash_reports import ReportHash
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
|
from tests import conftest
|
||||||
from tests.conftest import file
|
from tests.conftest import file
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,6 +106,7 @@ def test_export_csv_permitions(user: UserClient, user2: UserClient, client: Clie
|
||||||
assert len(csv_user) > 0
|
assert len(csv_user) > 0
|
||||||
assert len(csv_user2) == 0
|
assert len(csv_user2) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.mvp
|
@pytest.mark.mvp
|
||||||
def test_export_basic_snapshot(user: UserClient):
|
def test_export_basic_snapshot(user: UserClient):
|
||||||
"""Test export device information in a csv file."""
|
"""Test export device information in a csv file."""
|
||||||
|
@ -128,6 +132,30 @@ def test_export_basic_snapshot(user: UserClient):
|
||||||
assert fixture_csv[1][18] == export_csv[1][18], 'Computer information are not equal'
|
assert fixture_csv[1][18] == export_csv[1][18], 'Computer information are not equal'
|
||||||
assert fixture_csv[1][20:] == export_csv[1][20:], 'Computer information are not equal'
|
assert fixture_csv[1][20:] == export_csv[1][20:], 'Computer information are not equal'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_check_insert_hash(app: Devicehub, user: UserClient, client: Client):
|
||||||
|
"""Test export device information in a csv file."""
|
||||||
|
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
|
||||||
|
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||||
|
item='devices/',
|
||||||
|
accept='text/csv',
|
||||||
|
query=[('filter', {'type': ['Computer']})])
|
||||||
|
hash3 = hashlib.sha3_256(csv_str.encode('utf-8')).hexdigest()
|
||||||
|
assert ReportHash.query.filter_by(hash3=hash3).count() == 1
|
||||||
|
result, status = client.get(res=documents.DocumentDef.t, item='check/', query=[('hash', hash3)])
|
||||||
|
assert status.status_code == 200
|
||||||
|
assert result == True
|
||||||
|
|
||||||
|
ff = open('/tmp/test.csv', 'w')
|
||||||
|
ff.write(csv_str)
|
||||||
|
ff.close()
|
||||||
|
|
||||||
|
a= open('/tmp/test.csv').read()
|
||||||
|
assert hash3 == hashlib.sha3_256(a.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.mvp
|
@pytest.mark.mvp
|
||||||
def test_export_extended(app: Devicehub, user: UserClient):
|
def test_export_extended(app: Devicehub, user: UserClient):
|
||||||
"""Test a export device with all information and a lot of components."""
|
"""Test a export device with all information and a lot of components."""
|
||||||
|
|
Reference in New Issue