From da30cce6f41ef2967feee685030f908d4d31465a Mon Sep 17 00:00:00 2001 From: Doug Lovett Date: Tue, 30 May 2023 17:58:34 -0700 Subject: [PATCH] MHR API new batch manufacturer MHREG report. (#1363) * MHR API new batch manufacturer MHREG report. Signed-off-by: Doug Lovett * MHR API new batch manufacturer MHREG report. Signed-off-by: Doug Lovett --------- Signed-off-by: Doug Lovett --- mhr_api/src/mhr_api/config.py | 1 + .../src/mhr_api/models/registration_utils.py | 91 +++++++++++++++++++ mhr_api/src/mhr_api/reports/v2/report.py | 60 +++++++----- .../src/mhr_api/resources/v1/registrations.py | 62 ++++++++++++- .../services/abstract_storage_service.py | 1 + .../document_storage/storage_service.py | 5 +- .../mhr_api/utils/registration_validator.py | 4 + mhr_api/tests/unit/api/test_registrations.py | 31 +++++++ .../unit/models/test_registration_utils.py | 68 ++++++++++++++ .../reports/test_report_registration_v2.py | 22 +++++ .../unit/services/test_storage_service.py | 30 +++++- .../unit/utils/test_registration_validator.py | 7 ++ 12 files changed, 354 insertions(+), 28 deletions(-) create mode 100644 mhr_api/tests/unit/models/test_registration_utils.py diff --git a/mhr_api/src/mhr_api/config.py b/mhr_api/src/mhr_api/config.py index a50f0b6e5..b63ac92dc 100644 --- a/mhr_api/src/mhr_api/config.py +++ b/mhr_api/src/mhr_api/config.py @@ -178,6 +178,7 @@ class _Config(): # pylint: disable=too-few-public-methods GCP_CS_SA_SCOPES = os.getenv('GCP_CS_SA_SCOPES') GCP_CS_BUCKET_ID = os.getenv('mhr_search_result_report_dev') GCP_CS_BUCKET_ID_REGISTRATION = os.getenv('mhr_registration_report_dev') + GCP_CS_BUCKET_ID_BATCH = os.getenv('GCP_CS_BUCKET_ID_BATCH') # Pub/Sub GCP_PS_PROJECT_ID = os.getenv('GCP_PS_PROJECT_ID') GCP_PS_SEARCH_REPORT_TOPIC = os.getenv('GCP_PS_SEARCH_REPORT_TOPIC') diff --git a/mhr_api/src/mhr_api/models/registration_utils.py b/mhr_api/src/mhr_api/models/registration_utils.py index dd6794218..f8a15685f 100644 --- a/mhr_api/src/mhr_api/models/registration_utils.py +++ b/mhr_api/src/mhr_api/models/registration_utils.py @@ -47,6 +47,28 @@ SORT_ASCENDING = 'ascending' SORT_DESCENDING = 'descending' +QUERY_BATCH_MANUFACTURER_MHREG_DEFAULT = """ +select r.id, r.account_id, r.registration_ts, rr.id, rr.report_data, rr.batch_storage_url + from mhr_registrations r, mhr_manufacturers m, mhr_registration_reports rr + where r.id = rr.registration_id + and r.account_id = m.account_id + and r.registration_type = 'MHREG' + and r.registration_ts between (now() - interval '1 days') and now() +""" +QUERY_BATCH_MANUFACTURER_MHREG = """ +select r.id, r.account_id, r.registration_ts, rr.id, rr.report_data, rr.batch_storage_url + from mhr_registrations r, mhr_manufacturers m, mhr_registration_reports rr + where r.id = rr.registration_id + and r.account_id = m.account_id + and r.registration_type = 'MHREG' + and r.registration_ts between to_timestamp(:query_val1, 'YYYY-MM-DD HH24:MI:SS') + and to_timestamp(:query_val2, 'YYYY-MM-DD HH24:MI:SS') +""" +UPDATE_BATCH_REG_REPORT = """ +update mhr_registration_reports + set batch_storage_url = '{batch_url}' + where id in ({report_ids}) +""" QUERY_PPR_LIEN_COUNT = """ SELECT COUNT(base_registration_num) FROM mhr_lien_check_vw @@ -81,6 +103,7 @@ DOC_ID_QUALIFIED_CLAUSE = ', get_mhr_doc_qualified_id() AS doc_id' DOC_ID_MANUFACTURER_CLAUSE = ', get_mhr_doc_manufacturer_id() AS doc_id' DOC_ID_GOV_AGENT_CLAUSE = ', get_mhr_doc_gov_agent_id() AS doc_id' +BATCH_DOC_NAME_MANUFACTURER_MHREG = 'batch-manufacturer-mhreg-report-{time}.pdf' class AccountRegistrationParams(): @@ -318,3 +341,71 @@ def include_caution_note(notes, document_id: str) -> bool: elif latest_caution and note.get('documentType', '') not in ('CAUC', 'CAUE', 'CAU '): return False return latest_caution and not model_utils.date_elapsed(latest_caution.get('expiryDate')) + + +def get_batch_manufacturer_reg_report_data(start_ts: str = None, end_ts: str = None) -> dict: + """Get recent manufacturer MHREG registration report data for a batch report.""" + results_json = [] + query_s = QUERY_BATCH_MANUFACTURER_MHREG_DEFAULT + if start_ts and end_ts: + query_s = QUERY_BATCH_MANUFACTURER_MHREG + current_app.logger.debug(f'Using timestamp range {start_ts} to {end_ts}.') + else: + current_app.logger.debug('Using a default timestamp range of within the previous day.') + query = text(query_s) + result = None + if start_ts and end_ts: + start: str = start_ts[:19].replace('T', ' ') + end: str = end_ts[:19].replace('T', ' ') + current_app.logger.debug(f'start={start} end={end}') + result = db.session.execute(query, {'query_val1': start, 'query_val2': end}) + else: + result = db.session.execute(query) + rows = result.fetchall() + if rows is not None: + for row in rows: + batch_url = str(row[5]) if row[5] else '' + result_json = { + 'registrationId': int(row[0]), + 'accountId': str(row[1]), + 'reportId': int(row[3]), + 'reportData': row[4], + 'batchStorageUrl': batch_url + } + results_json.append(result_json) + if results_json: + current_app.logger.debug(f'Found {len(results_json)} manufacturer MHREG registrations.') + else: + current_app.logger.debug('No manufacturer MHREG registrations found within the timestamp range.') + return results_json + + +def update_reg_report_batch_url(json_data: dict, batch_url: str) -> int: + """Set the mhr registration reports batch storage url for the recent registrations in json_data.""" + update_count: int = 0 + if not json_data: + return update_count + query_s = UPDATE_BATCH_REG_REPORT + report_ids: str = '' + for report in json_data: + update_count += 1 + if report_ids != '': + report_ids += ',' + report_ids += str(report.get('reportId')) + query_s = query_s.format(batch_url=batch_url, report_ids=report_ids) + current_app.logger.debug(f'Executing update query {query_s}') + query = text(query_s) + result = db.session.execute(query) + db.session.commit() + if result: + current_app.logger.debug(f'Updated {update_count} manufacturer report registrations batch url to {batch_url}.') + return update_count + + +def get_batch_storage_name_manufacturer_mhreg(): + """Get a search document storage name in the format YYYY/MM/DD/batch-manufacturer-mhreg-report-time.pdf.""" + now_ts = model_utils.now_ts() + name = now_ts.isoformat()[:10] + time = str(now_ts.hour) + '_' + str(now_ts.minute) + name = name.replace('-', '/') + '/' + BATCH_DOC_NAME_MANUFACTURER_MHREG.format(time=time) + return name diff --git a/mhr_api/src/mhr_api/reports/v2/report.py b/mhr_api/src/mhr_api/reports/v2/report.py index 546164c4d..bf632548c 100755 --- a/mhr_api/src/mhr_api/reports/v2/report.py +++ b/mhr_api/src/mhr_api/reports/v2/report.py @@ -78,10 +78,7 @@ def get_pdf(self, report_type=None): .format(self._account_id, self._report_key, url)) meta_data = report_utils.get_report_meta_data(self._report_key) files = report_utils.get_report_files(data, self._report_key) - headers = {} - token = GoogleAuthService.get_report_api_token() - if token: - headers['Authorization'] = 'Bearer {}'.format(token) + headers = Report.get_headers() response = requests.post(url=url, headers=headers, data=meta_data, files=files) current_app.logger.debug('Account {0} report type {1} response status: {2}.' .format(self._account_id, self._report_key, response.status_code)) @@ -104,10 +101,7 @@ def get_search_pdf(self): .format(self._account_id, self._report_key, url)) meta_data = report_utils.get_report_meta_data(self._report_key) files = report_utils.get_report_files(data, self._report_key) - headers = {} - token = GoogleAuthService.get_report_api_token() - if token: - headers['Authorization'] = 'Bearer {}'.format(token) + headers = Report.get_headers() response_reg = requests.post(url=url, headers=headers, data=meta_data, files=files) current_app.logger.debug('Account {0} report type {1} response status: {2}.' .format(self._account_id, self._report_key, response_reg.status_code)) @@ -141,10 +135,7 @@ def get_registration_cover_pdf(self): url = current_app.config.get('REPORT_SVC_URL') + SINGLE_URI meta_data = report_utils.get_report_meta_data(self._report_key) files = report_utils.get_report_files(data, self._report_key, False) - headers = {} - token = GoogleAuthService.get_report_api_token() - if token: - headers['Authorization'] = 'Bearer {}'.format(token) + headers = Report.get_headers() response_cover = requests.post(url=url, headers=headers, data=meta_data, files=files) current_app.logger.debug('Account {0} report type {1} response status: {2}.' .format(self._account_id, self._report_key, response_cover.status_code)) @@ -165,10 +156,7 @@ def get_registration_staff_pdf(self): url = current_app.config.get('REPORT_SVC_URL') + SINGLE_URI meta_data = report_utils.get_report_meta_data(self._report_key) files = report_utils.get_report_files(data, self._report_key, False) - headers = {} - token = GoogleAuthService.get_report_api_token() - if token: - headers['Authorization'] = 'Bearer {}'.format(token) + headers = Report.get_headers() response_cover = requests.post(url=url, headers=headers, data=meta_data, files=files) current_app.logger.debug('Account {0} report type {1} response status: {2}.' .format(self._account_id, self._report_key, response_cover.status_code)) @@ -200,19 +188,41 @@ def get_registration_staff_pdf(self): current_app.logger.error('Account {0} response status: {1} error: {2}.' .format(self._account_id, response_reg.status_code, content)) return jsonify(message=content), response_reg.status_code, None - # 3: Merge cover letter and registraiton reports. + # 3: Merge cover letter and registration reports. + files = [] + files.append(response_cover.content) + files.append(response_reg.content) + return Report.batch_merge(files) + + @staticmethod + def get_headers() -> dict: + """Build the report service request headers.""" + headers = {} + token = GoogleAuthService.get_report_api_token() + if token: + headers['Authorization'] = 'Bearer {}'.format(token) + return headers + + @staticmethod + def batch_merge(pdf_list): + """Merge a list of pdf files into a single pdf.""" + if not pdf_list: + return None + current_app.logger.debug(f'Setting up batch merge for {len(pdf_list)} files.') + count: int = 0 + files = {} + for pdf in pdf_list: + count += 1 + filename = 'file' + str(count) + '.pdf' + files[filename] = pdf + headers = Report.get_headers() url = current_app.config.get('REPORT_SVC_URL') + MERGE_URI - files = { - 'pdf1.pdf': response_cover.content, - 'pdf2.pdf': response_reg.content - } response = requests.post(url=url, headers=headers, files=files) - current_app.logger.debug('Merge cover and registration reports response status: {0}.' - .format(response.status_code)) + current_app.logger.debug('Batch merge reports response status: {0}.'.format(response.status_code)) if response.status_code != HTTPStatus.OK: content = ResourceErrorCodes.REPORT_ERR + ': ' + response.content.decode('ascii') - current_app.logger.error('Account {0} merge response status: {1} error: {2}.' - .format(self._account_id, response.status_code, content)) + current_app.logger.error('Batch merge response status: {0} error: {1}.'.format(response.status_code, + content)) return jsonify(message=content), response.status_code, None return response.content, response.status_code, {'Content-Type': 'application/pdf'} diff --git a/mhr_api/src/mhr_api/resources/v1/registrations.py b/mhr_api/src/mhr_api/resources/v1/registrations.py index 215b9788d..966d35d60 100755 --- a/mhr_api/src/mhr_api/resources/v1/registrations.py +++ b/mhr_api/src/mhr_api/resources/v1/registrations.py @@ -24,12 +24,16 @@ from mhr_api.exceptions import BusinessException, DatabaseException from mhr_api.services.authz import authorized, authorized_role, is_staff, is_all_staff_account, REGISTER_MH from mhr_api.services.authz import is_reg_staff_account, get_group, MANUFACTURER_GROUP -from mhr_api.models import MhrRegistration, MhrManufacturer +from mhr_api.models import MhrRegistration, MhrManufacturer, registration_utils as model_reg_utils from mhr_api.models.registration_utils import AccountRegistrationParams +from mhr_api.reports import get_callback_pdf +from mhr_api.reports.v2.report import Report from mhr_api.reports.v2.report_utils import ReportTypes from mhr_api.resources import utils as resource_utils, registration_utils as reg_utils from mhr_api.services.payment import TransactionTypes from mhr_api.services.payment.exceptions import SBCPaymentException +from mhr_api.services.utils.exceptions import ReportDataException, ReportException, StorageException +from mhr_api.services.document_storage.storage_service import DocumentTypes, GoogleStorageService bp = Blueprint('REGISTRATIONS1', # pylint: disable=invalid-name @@ -209,3 +213,59 @@ def get_registrations(mhr_number: str): # pylint: disable=too-many-return-state 'GET MH registration id=' + mhr_number) except Exception as default_exception: # noqa: B902; return nicer default error return resource_utils.default_exception_response(default_exception) + + +@bp.route('/batch/manufacturer', methods=['GET', 'OPTIONS']) +@cross_origin(origin='*') +def get_batch_manufacturer_registrations(): # pylint: disable=too-many-return-statements, too-many-branches + """Get batch manufacturer registrations report for .""" + try: + current_app.logger.info('getting batch manufacturer registrations') + # Authenticate with request api key + if not resource_utils.valid_api_key(request): + return resource_utils.unauthorized_error_response('batch manufacturer registrations report') + start_ts: str = request.args.get(model_reg_utils.START_TS_PARAM, None) + end_ts: str = request.args.get(model_reg_utils.END_TS_PARAM, None) + if start_ts and end_ts: + start_ts = resource_utils.remove_quotes(start_ts) + end_ts = resource_utils.remove_quotes(end_ts) + current_app.logger.debug(f'Using request timestamp range {start_ts} to {end_ts}') + registrations = model_reg_utils.get_batch_manufacturer_reg_report_data(start_ts, end_ts) + if not registrations: + current_app.logger.debug(f'No manufacturer registrations found for timestamp range {start_ts} to {end_ts}') + return '', HTTPStatus.NO_CONTENT + # Generate individual registration reports with a cover letter + reports = [] + for reg in registrations: + raw_data, status_code, headers = get_callback_pdf(reg.get('reportData'), + reg.get('accountId'), + ReportTypes.MHR_REGISTRATION_STAFF, + None, + None) + if status_code in (HTTPStatus.OK, HTTPStatus.CREATED): + reports.append(raw_data) + else: + current_app.logger.error(str(reg.get('registrationId')) + ' report api call failed: ' + + raw_data.get_data(as_text=True)) + if reports and len(reports) > 1: + raw_data, status_code, headers = Report.batch_merge(reports) + if status_code not in (HTTPStatus.OK, HTTPStatus.CREATED): + current_app.logger.error('Batch manufacturer report merge call failed: ' + raw_data.get_data(as_text=True)) + return raw_data, status_code, headers + batch_storage_url = model_reg_utils.get_batch_storage_name_manufacturer_mhreg() + current_app.logger.info(f'Saving batch manufacturer registration report to: {batch_storage_url}.') + GoogleStorageService.save_document(batch_storage_url, raw_data, DocumentTypes.BATCH_REGISTRATION) + model_reg_utils.update_reg_report_batch_url(registrations, batch_storage_url) + return raw_data, HTTPStatus.OK, headers + except ReportException as report_err: + return resource_utils.service_exception_response('Batch manufacturer report API error: ' + str(report_err)) + except ReportDataException as report_data_err: + return resource_utils.service_exception_response('Batch manufacturerreport API data error: ' + + str(report_data_err)) + except StorageException as storage_err: + return resource_utils.service_exception_response('Batch manufacturer report storage API error: ' + + str(storage_err)) + except DatabaseException as db_exception: + return resource_utils.db_exception_response(db_exception, None, 'Batch manufacturer report error') + except Exception as default_exception: # noqa: B902; return nicer default error + return resource_utils.default_exception_response(default_exception) diff --git a/mhr_api/src/mhr_api/services/abstract_storage_service.py b/mhr_api/src/mhr_api/services/abstract_storage_service.py index f0b15d342..3aac6b62c 100644 --- a/mhr_api/src/mhr_api/services/abstract_storage_service.py +++ b/mhr_api/src/mhr_api/services/abstract_storage_service.py @@ -22,6 +22,7 @@ class DocumentTypes(BaseEnum): SEARCH_RESULTS = 'SEARCH_RESULTS' REGISTRATION = 'REGISTRATION' + BATCH_REGISTRATION = 'BATCH_REGISTRATION' class StorageService(ABC): # pylint: disable=too-few-public-methods diff --git a/mhr_api/src/mhr_api/services/document_storage/storage_service.py b/mhr_api/src/mhr_api/services/document_storage/storage_service.py index e15ac0bfe..5f9c359f5 100644 --- a/mhr_api/src/mhr_api/services/document_storage/storage_service.py +++ b/mhr_api/src/mhr_api/services/document_storage/storage_service.py @@ -39,6 +39,7 @@ class GoogleStorageService(StorageService): # pylint: disable=too-few-public-me # Google cloud storage configuration. GCP_BUCKET_ID = str(os.getenv('GCP_CS_BUCKET_ID')) GCP_BUCKET_ID_REGISTRATION = str(os.getenv('GCP_CS_BUCKET_ID_REGISTRATION')) + GCP_BUCKET_ID_BATCH = str(os.getenv('GCP_CS_BUCKET_ID_BATCH')) GCP_URL = str(os.getenv('GCP_CS_URL', 'https://storage.googleapis.com')) DOC_URL = GCP_URL + '/storage/v1/b/{bucket_id}/o/{name}' GET_DOC_URL = DOC_URL + '?alt=media' @@ -135,6 +136,8 @@ def __get_bucket_id(cls, doc_type: str = None): return cls.GCP_BUCKET_ID if doc_type == DocumentTypes.REGISTRATION: return cls.GCP_BUCKET_ID_REGISTRATION + if doc_type == DocumentTypes.BATCH_REGISTRATION: + return cls.GCP_BUCKET_ID_BATCH return cls.GCP_BUCKET_ID @classmethod @@ -178,7 +181,7 @@ def __call_cs_api(cls, method: str, name: str, data=None, doc_type: str = None): bucket = storage_client.bucket(cls.__get_bucket_id(doc_type)) blob = bucket.blob(name) if method == HTTP_POST: - blob.upload_from_string(data) + blob.upload_from_string(data=data, content_type='application/pdf') return blob.time_created if method == HTTP_GET: contents = blob.download_as_bytes() diff --git a/mhr_api/src/mhr_api/utils/registration_validator.py b/mhr_api/src/mhr_api/utils/registration_validator.py index 682e987a3..375bd0b8b 100644 --- a/mhr_api/src/mhr_api/utils/registration_validator.py +++ b/mhr_api/src/mhr_api/utils/registration_validator.py @@ -58,6 +58,8 @@ RESERVE_NUMBER_REQUIRED = 'The location Indian Reserve number is required for this registration. ' OWNERS_JOINT_INVALID = 'The owner group must contain at least 2 owners. ' OWNERS_COMMON_INVALID = 'Each COMMON owner group must contain exactly 1 owner. ' +OWNERS_COMMON_SOLE_INVALID = 'SOLE owner group tenancy type is not allowed when there is more than 1 ' \ + 'owner group. Use COMMON instead. ' LOCATION_DEALER_REQUIRED = 'Location dealer/manufacturer name is required for this registration. ' STATUS_CONFIRMATION_REQUIRED = 'The land status confirmation is required for this registration. ' LOCATION_PARK_NAME_REQUIRED = 'Location park name is required for this registration. ' @@ -550,6 +552,8 @@ def validate_owner_group(group, int_required: bool = False): elif orig_type != 'NA' and tenancy_type == Db2Owngroup.TenancyTypes.COMMON and \ (not group.get('owners') or len(group.get('owners')) > 1): error_msg += OWNERS_COMMON_INVALID + elif orig_type == MhrTenancyTypes.SOLE and int_required: + error_msg += OWNERS_COMMON_SOLE_INVALID return error_msg diff --git a/mhr_api/tests/unit/api/test_registrations.py b/mhr_api/tests/unit/api/test_registrations.py index b2213f376..14cf03eae 100644 --- a/mhr_api/tests/unit/api/test_registrations.py +++ b/mhr_api/tests/unit/api/test_registrations.py @@ -146,6 +146,13 @@ ('Invalid MHR Number', [MHR_ROLE], HTTPStatus.NOT_FOUND, '2523', 'TESTXXXX'), ('Invalid request Staff no account', [MHR_ROLE, STAFF_ROLE], HTTPStatus.BAD_REQUEST, None, '150062') ] +# testdata pattern is ({description}, {start_ts}, {end_ts}, {status}, {has_key}) +TEST_BATCH_MANUFACTURER_MHREG_DATA = [ + ('Unauthorized', None, None, HTTPStatus.UNAUTHORIZED, False), + ('Valid no data', '2023-02-25T07:01:00+00:00', '2023-02-26T07:01:00+00:00', HTTPStatus.NO_CONTENT, True), + ('Valid data', '2023-05-25T07:01:00+00:00', '2023-05-26T07:01:00+00:00', HTTPStatus.OK, True), + ('Valid default interval may have data', None, None, HTTPStatus.OK, True) +] # testdata pattern is ({desc}, {roles}, {status}, {sort_criteria}, {sort_direction}) TEST_GET_ACCOUNT_DATA_SORT2 = [ ('Sort mhr number', [MHR_ROLE, STAFF_ROLE], HTTPStatus.OK, reg_utils.MHR_NUMBER_PARAM, None) @@ -375,3 +382,27 @@ def test_get_account_registrations_filter_date(session, client, jwt, desc, accou assert registration['clientReferenceId'] is not None assert registration['ownerNames'] is not None assert registration['path'] is not None + + +@pytest.mark.parametrize('desc,start_ts,end_ts,status,has_key',TEST_BATCH_MANUFACTURER_MHREG_DATA) +def test_get_batch_mhreg_manufacturer_report(session, client, jwt, desc, start_ts, end_ts, status, has_key): + """Assert that a get account registrations summary list endpoint with filtering works as expected.""" + # setup + apikey = current_app.config.get('SUBSCRIPTION_API_KEY') + params: str = '' + if has_key: + params += '?x-apikey=' + apikey + if start_ts and end_ts: + start: str = reg_utils.START_TS_PARAM + end: str = reg_utils.END_TS_PARAM + if has_key: + params += f'&{start}={start_ts}&{end}={end_ts}' + else: + params += f'?{start}={start_ts}&{end}={end_ts}' + # test + rv = client.get('/api/v1/registrations/batch/manufacturer' + params) + # check + if desc == 'Valid default interval may have data': + assert rv.status_code == status or rv.status_code == HTTPStatus.NO_CONTENT + else: + assert rv.status_code == status diff --git a/mhr_api/tests/unit/models/test_registration_utils.py b/mhr_api/tests/unit/models/test_registration_utils.py new file mode 100644 index 000000000..15635bda6 --- /dev/null +++ b/mhr_api/tests/unit/models/test_registration_utils.py @@ -0,0 +1,68 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Test Suite to ensure the model utility functions are working as expected.""" +from datetime import timedelta as _timedelta + +import pytest + +from flask import current_app + +from mhr_api.models import utils as model_utils, registration_utils as reg_utils + + +# testdata pattern is ({start_ts}, {end_ts}) +TEST_DATA_MANUFACTURER_MHREG = [ + ('2023-05-25T07:01:00+00:00', '2023-05-26T07:01:00+00:00'), + (None, '2023-05-26T07:01:00+00:00'), + ('2023-05-25T07:01:00+00:00', None), + (None, None) +] +# testdata pattern is ({start_ts}, {end_ts}) +TEST_DATA_MANUFACTURER_MHREG_UPDATE = [ + ('2023-05-25T07:01:00+00:00', '2023-05-26T07:01:00+00:00'), + (None, None) +] + + +@pytest.mark.parametrize('start_ts,end_ts', TEST_DATA_MANUFACTURER_MHREG) +def test_get_batch_manufacturer_reg_report_data(session, start_ts, end_ts): + """Assert that fetching manufacturer MHREG data by optional timestamp range works as expected.""" + results_json = reg_utils.get_batch_manufacturer_reg_report_data(start_ts, end_ts) + if results_json: + for result in results_json: + assert result.get('registrationId') + assert result.get('accountId') + assert result.get('reportId') + assert result.get('reportData') + assert 'batchStorageUrl' in result + + +def test_get_batch_manufacturer_reg_report_name(session): + """Assert that fetching manufacturer MHREG data by optional timestamp range works as expected.""" + now_ts = model_utils.now_ts() + time = str(now_ts.hour) + '_' + str(now_ts.minute) + test_name: str = reg_utils.BATCH_DOC_NAME_MANUFACTURER_MHREG.format(time=time) + storage_name: str = reg_utils.get_batch_storage_name_manufacturer_mhreg() + assert storage_name.find(test_name) > -1 + + +@pytest.mark.parametrize('start_ts,end_ts', TEST_DATA_MANUFACTURER_MHREG_UPDATE) +def test_update_manufacturer_reg_report_batch_url(session, start_ts, end_ts): + """Assert that batch updating of the registration report batch storage url works as expected.""" + results_json = reg_utils.get_batch_manufacturer_reg_report_data(start_ts, end_ts) + if results_json: + batch_url: str = reg_utils.get_batch_storage_name_manufacturer_mhreg() + update_count: int = reg_utils.update_reg_report_batch_url(results_json, batch_url) + assert update_count > 0 + diff --git a/mhr_api/tests/unit/reports/test_report_registration_v2.py b/mhr_api/tests/unit/reports/test_report_registration_v2.py index 4e04c6fce..25ccd3c80 100755 --- a/mhr_api/tests/unit/reports/test_report_registration_v2.py +++ b/mhr_api/tests/unit/reports/test_report_registration_v2.py @@ -36,6 +36,7 @@ REGISTRATON_MAIL_PDFFILE = 'tests/unit/reports/data/registration-mail-example.pdf' REGISTRATON_COVER_PDFFILE = 'tests/unit/reports/data/registration-cover-example.pdf' REGISTRATON_STAFF_PDFFILE = 'tests/unit/reports/data/registration-staff-example.pdf' +REGISTRATON_BATCH_PDFFILE = 'tests/unit/reports/data/registration-batch-example.pdf' TRANSFER_TEST_SO_DATAFILE = 'tests/unit/reports/data/trans-test-example.json' TRANSFER_TEST_SO_PDFFILE = 'tests/unit/reports/data/trans-test-example-so.pdf' @@ -242,6 +243,27 @@ def test_staff_registration(session, client, jwt): check_response(content, status, REGISTRATON_STAFF_PDFFILE) +def test_batch_merge(session, client, jwt): + """Assert that generation of a batch merge of registration reports is as expected.""" + # setup + if is_report_v2(): + reports = [] + json_data = get_json_from_file(REGISTRATON_TEST_DATAFILE) + create_ts = json_data.get('createDateTime') + report = Report(json_data, '2523', ReportTypes.MHR_REGISTRATION_COVER, 'Account Name') + content, status, headers = report.get_pdf() + reports.append(content) + json_data['createDateTime'] = create_ts + report = Report(json_data, '2523', ReportTypes.MHR_REGISTRATION, 'Account Name') + content, status, headers = report.get_pdf() + reports.append(content) + # test + content, status, headers = Report.batch_merge(reports) + assert headers + # verify + check_response(content, status, REGISTRATON_BATCH_PDFFILE) + + def test_exemption_res(session, client, jwt): """Assert that generation of a test report is as expected.""" # setup diff --git a/mhr_api/tests/unit/services/test_storage_service.py b/mhr_api/tests/unit/services/test_storage_service.py index b6fcb162b..1082e811a 100644 --- a/mhr_api/tests/unit/services/test_storage_service.py +++ b/mhr_api/tests/unit/services/test_storage_service.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """Google storage service tests.""" +from flask import current_app + from mhr_api.services.abstract_storage_service import DocumentTypes from mhr_api.services.document_storage.storage_service import GoogleStorageService @@ -23,6 +25,8 @@ TEST_REGISTRATION_DOC_NAME = 'test_reg_doc.pdf' TEST_REGISTRATION_DATAFILE = 'tests/unit/services/test_reg_doc.pdf' TEST_REGISTRATION_SAVE_DOC_NAME = '2022/05/06/registration-ut-001.pdf' +TEST_BATCH_REGISTRATION_SAVE_DOC_NAME = '2023/05/29/batch-manufacturer-mhreg-report-ut-001.pdf' +TEST_BATCH_REGISTRATION_DATAFILE = 'tests/unit/services/test_batch_reg_doc.pdf' def test_cs_save_search_document_http(session): @@ -58,7 +62,6 @@ def test_cs_save_registration_document(session): response = GoogleStorageService.save_document(TEST_REGISTRATION_SAVE_DOC_NAME, raw_data, DocumentTypes.REGISTRATION) - print(response) assert response @@ -95,3 +98,28 @@ def test_cs_get_registration_document(session): def test_cs_delete_search_document(session): """Assert that deleting a search bucket document from google cloud storage works as expected.""" response = GoogleStorageService.delete_document(TEST_SAVE_DOC_NAME2, DocumentTypes.SEARCH_RESULTS) + + +def test_save_batch_registration_document(session): + """Assert that saving a batch registration pdf to google cloud storage works as expected.""" + bucket: str = current_app.config.get('GCP_CS_BUCKET_ID_BATCH') + current_app.logger.debug(f'Testing saving to bucket={bucket}') + raw_data = None + with open(TEST_REGISTRATION_DATAFILE, 'rb') as data_file: + raw_data = data_file.read() + data_file.close() + + response = GoogleStorageService.save_document(TEST_BATCH_REGISTRATION_SAVE_DOC_NAME, raw_data, + DocumentTypes.BATCH_REGISTRATION) + assert response + + +def test_cs_get_batch_registration_document(session): + """Assert that getting a batch registration pdf from google cloud storage works as expected.""" + raw_data = GoogleStorageService.get_document(TEST_BATCH_REGISTRATION_SAVE_DOC_NAME, + DocumentTypes.BATCH_REGISTRATION) + assert raw_data + assert len(raw_data) > 0 + with open(TEST_BATCH_REGISTRATION_DATAFILE, "wb") as pdf_file: + pdf_file.write(raw_data) + pdf_file.close() diff --git a/mhr_api/tests/unit/utils/test_registration_validator.py b/mhr_api/tests/unit/utils/test_registration_validator.py index e6ee9bcc3..b37123c78 100644 --- a/mhr_api/tests/unit/utils/test_registration_validator.py +++ b/mhr_api/tests/unit/utils/test_registration_validator.py @@ -245,6 +245,8 @@ ('Valid SO', True, None, None, SO_VALID, None), ('Valid JT', True, None, None, JT_VALID, None), ('Invalid TC no owner', False, 1, 2, TC_GROUPS_VALID, validator.OWNERS_COMMON_INVALID), + ('Invalid TC SO group', False, 1, 2, TC_GROUPS_VALID, validator.OWNERS_COMMON_SOLE_INVALID), + ('Invalid TC 2 owners', False, 1, 2, TC_GROUPS_VALID, validator.OWNERS_COMMON_INVALID), ('Invalid TC only 1 group', False, 2, 2, TC_GROUPS_VALID, validator.GROUP_COMMON_INVALID), ('Invalid TC numerator missing', False, None, 2, TC_GROUPS_VALID, validator.GROUP_NUMERATOR_MISSING), ('Invalid TC numerator < 1', False, 0, 2, TC_GROUPS_VALID, validator.GROUP_NUMERATOR_MISSING), @@ -361,6 +363,11 @@ def test_validate_registration_group(session, desc, valid, numerator, denominato del json_data['ownerGroups'][1] elif desc == 'Invalid TC no owner': json_data['ownerGroups'][0]['owners'] = [] + elif desc == 'Invalid TC SO group': + json_data['ownerGroups'][0]['type'] = 'SOLE' + elif desc == 'Invalid TC 2 owners': + owners = copy.deepcopy(JT_GROUP_VALID).get('owners') + json_data['ownerGroups'][0]['owners'] = owners elif groups[0].get('type') == MhrTenancyTypes.COMMON: for group in json_data.get('ownerGroups'): if not numerator: