From c332b6ff550d25a0e09ae704d8e0e0eef74f3c18 Mon Sep 17 00:00:00 2001 From: Victor <97096480+VictorDelCampo@users.noreply.github.com> Date: Mon, 21 Feb 2022 10:30:04 +0100 Subject: [PATCH] Stl 1821 erica api v.2 (#5) * STL-1821 | API v1 restructured, leaned to FastAPI * STL-1821 | Removed prefixes from tax and unlock_code ressources * STL-1821 | Added v2 * STL-1821 | Variables renamed * STL-1821 | Changes after 1st review * STL-1821 | Changes after 2nd review * STL-1821 | Removed code smell * STL-1821 | Removed code smell * STL-1821 | Added FastAPI-Versioning | Update unlock_code to fsc in v2 | Model classes in different files * STL-1821 | Removed code smells * STL-1821 | Removed code duplication * STL-1821 | Removed FastAPI-Versioning library and usage | Added specific response models for HTTP 200 in v2 | Reverted pipfiles to match origin/main * STL-1821 | Removed code smell * STL-1821 | Pipfile.lock everted to state in origin/main * STL-1821 | Added enum Status | Modified base class for get from queue * STL-1821 | Changed 01 to v2 | Added "Success.." keyword to response models | Update enum status --- erica/__init__.py | 6 +- erica/api/api.py | 9 + erica/api/v1/api_v1.py | 15 ++ .../v1/endpoints/grundsteuer/grundsteuer.py | 19 ++ erica/api/v1/endpoints/ping.py | 10 ++ erica/api/v1/endpoints/rente/address.py | 17 ++ erica/api/v1/endpoints/rente/est.py | 49 +++++ erica/api/v1/endpoints/rente/tax.py | 31 ++++ erica/api/v1/endpoints/rente/unlock_code.py | 65 +++++++ erica/api/v2/api_v2.py | 16 ++ erica/api/v2/endpoints/ping.py | 10 ++ erica/api/v2/endpoints/rente/est.py | 64 +++++++ erica/api/v2/endpoints/rente/fsc.py | 97 ++++++++++ erica/api/v2/endpoints/rente/tax.py | 45 +++++ erica/api/v2/responses/model.py | 40 +++++ erica/pyeric/utils.py | 7 + erica/request_processing/eric_mapper.py | 68 +++---- .../{ => erica_input/v1}/erica_input.py | 62 ++++--- .../erica_input/v2/erica_input.py | 87 +++++++++ .../request_processing/requests_controller.py | 2 +- erica/routes.py | 169 ------------------ tests/request_processing/test_eric_mapper.py | 5 +- tests/request_processing/test_erica_input.py | 5 +- .../test_requests_controller.py | 2 +- tests/test_routes.py | 16 +- tests/test_sample_data_validations.py | 2 +- tests/utils.py | 2 +- 27 files changed, 658 insertions(+), 262 deletions(-) create mode 100644 erica/api/api.py create mode 100644 erica/api/v1/api_v1.py create mode 100644 erica/api/v1/endpoints/grundsteuer/grundsteuer.py create mode 100644 erica/api/v1/endpoints/ping.py create mode 100644 erica/api/v1/endpoints/rente/address.py create mode 100644 erica/api/v1/endpoints/rente/est.py create mode 100644 erica/api/v1/endpoints/rente/tax.py create mode 100644 erica/api/v1/endpoints/rente/unlock_code.py create mode 100644 erica/api/v2/api_v2.py create mode 100644 erica/api/v2/endpoints/ping.py create mode 100644 erica/api/v2/endpoints/rente/est.py create mode 100644 erica/api/v2/endpoints/rente/fsc.py create mode 100644 erica/api/v2/endpoints/rente/tax.py create mode 100644 erica/api/v2/responses/model.py rename erica/request_processing/{ => erica_input/v1}/erica_input.py (98%) create mode 100644 erica/request_processing/erica_input/v2/erica_input.py delete mode 100644 erica/routes.py diff --git a/erica/__init__.py b/erica/__init__.py index 4477a34a..660c2b0f 100644 --- a/erica/__init__.py +++ b/erica/__init__.py @@ -4,6 +4,7 @@ from prometheus_client import Gauge from prometheus_fastapi_instrumentator import Instrumentator +from erica.api.api import api_router from erica.pyeric.eric import verify_using_stick app = FastAPI() @@ -34,7 +35,10 @@ def get(cls): up_metric.labels(job='erica').set(1.0) # Always 1 when the erica_app is running. up_metric.labels(job='dongle').set_function(DongleStatus.get) +# Add router +app.include_router(api_router) + # Add default metrics and expose endpoint. Instrumentator().instrument(app).expose(app) -from erica import routes + diff --git a/erica/api/api.py b/erica/api/api.py new file mode 100644 index 00000000..1eb3ee6d --- /dev/null +++ b/erica/api/api.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter + +from erica.api.v1.api_v1 import api_router_01 +from erica.api.v2.api_v2 import api_router_02 + +api_router = APIRouter() + +api_router.include_router(api_router_01) +api_router.include_router(api_router_02) diff --git a/erica/api/v1/api_v1.py b/erica/api/v1/api_v1.py new file mode 100644 index 00000000..d8c16e14 --- /dev/null +++ b/erica/api/v1/api_v1.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter + +from erica.api.v1.endpoints import ping +from erica.api.v1.endpoints.rente import tax, est, address, unlock_code +from erica.api.v1.endpoints.grundsteuer import grundsteuer + +api_router_01 = APIRouter() +api_router_01.prefix = '/01' + +api_router_01.include_router(ping.router, prefix="/ping", tags=["Ping"]) +api_router_01.include_router(address.router, prefix="/address", tags=["Adresse"]) +api_router_01.include_router(est.router, tags=["Steuererklärung"]) +api_router_01.include_router(grundsteuer.router, prefix="/grundsteuer", tags=["Grundsteuer"]) +api_router_01.include_router(tax.router, tags=["Finanzverwaltung"]) +api_router_01.include_router(unlock_code.router, tags=["Freischaltcode"]) diff --git a/erica/api/v1/endpoints/grundsteuer/grundsteuer.py b/erica/api/v1/endpoints/grundsteuer/grundsteuer.py new file mode 100644 index 00000000..76a5cff8 --- /dev/null +++ b/erica/api/v1/endpoints/grundsteuer/grundsteuer.py @@ -0,0 +1,19 @@ +from fastapi import status, APIRouter +from erica.request_processing.erica_input.v1.erica_input import GrundsteuerData +router = APIRouter() + + +@router.post('/', status_code=status.HTTP_201_CREATED) +def send_grundsteuer(grundsteuer_data: GrundsteuerData, include_elster_responses: bool = False): + """ + The Grundsteuer data is validated and then send to ELSTER using ERiC. If it is successful, this should return a 201 + HTTP response with {'transfer_ticket': str, 'pdf': str}. The pdf is base64 encoded binary data of the pdf + If there is any error with the validation, this should return a 400 response. If the validation failed with + {‘code’ : int,‘message’: str,‘description’: str, ‘validation_problems’ : [{‘code’: int, ‘message’: str}]} + or a 400 repsonse for other client errors and a 500 response for server errors with + {‘code’ : int,‘message’: str,‘description’: str} + + :param grundsteuer_data: the JSON input data for the land tax declaration + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + return {"request": "successful"} diff --git a/erica/api/v1/endpoints/ping.py b/erica/api/v1/endpoints/ping.py new file mode 100644 index 00000000..aa2a434f --- /dev/null +++ b/erica/api/v1/endpoints/ping.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get('/') +def ping(): + """Simple route that can be used to check if the app has started. + """ + return 'pong' diff --git a/erica/api/v1/endpoints/rente/address.py b/erica/api/v1/endpoints/rente/address.py new file mode 100644 index 00000000..78f87ce9 --- /dev/null +++ b/erica/api/v1/endpoints/rente/address.py @@ -0,0 +1,17 @@ +from fastapi import status, APIRouter +from erica.request_processing.erica_input.v1.erica_input import GetAddressData + +router = APIRouter() + + +@router.post('/', status_code=status.HTTP_200_OK) +def get_address(get_address: GetAddressData, include_elster_responses: bool = False): + """ + The address data of the given idnr is requested at Elster and returned. Be aware, that you need a permission + (aka an activated unlock_code) to query a person's data. + + :param get_address: the JSON input data for the request + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + # For now, we do not allow data requests as we cannot guarantee that Elster already has the relevant data gathered + raise NotImplementedError() diff --git a/erica/api/v1/endpoints/rente/est.py b/erica/api/v1/endpoints/rente/est.py new file mode 100644 index 00000000..00c6d54b --- /dev/null +++ b/erica/api/v1/endpoints/rente/est.py @@ -0,0 +1,49 @@ +import logging + +from fastapi import HTTPException, status, APIRouter +from erica.request_processing.erica_input.v1.erica_input import EstData +from erica.pyeric.eric_errors import EricProcessNotSuccessful +from erica.request_processing.requests_controller import EstValidationRequestController, EstRequestController + +router = APIRouter() + + +@router.get('/est_validations') +def validate_est(est: EstData, include_elster_responses: bool = False): + """ + Data for a Est is validated using ERiC. If the validation is successful then this should return + a 200 HTTP response with {'success': bool, 'est': est}. Otherwise this should return a 400 response if the + validation failed with {‘code’ : int,‘message’: str,‘description’: str,‘‘validation_problems’ : [{‘code’: int, + ‘message’: str}]} or a 400 response for other client errors and a 500 response for server errors with {‘code’ : + int, ‘message’: str, ‘description’: str} + + :param est: the JSON input data for the ESt + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + try: + request = EstValidationRequestController(est, include_elster_responses) + return request.process() + except EricProcessNotSuccessful as e: + logging.getLogger().info("Could not validate est", exc_info=True) + raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) + + +@router.post('/ests', status_code=status.HTTP_201_CREATED) +def send_est(est: EstData, include_elster_responses: bool = False): + """ + An Est is validated and then send to ELSTER using ERiC. If it is successful, this should return a 200 HTTP + response with {'transfer_ticket': str, 'pdf': str}. The pdf is base64 encoded binary data of the pdf + If there is any error with the validation, this should return a 400 response. If the validation failed with + {‘code’ : int,‘message’: str,‘description’: str, ‘validation_problems’ : [{‘code’: int, ‘message’: str}]} + or a 400 repsonse for other client errors and a 500 response for server errors with + {‘code’ : int,‘message’: str,‘description’: str} + + :param est: the JSON input data for the ESt + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + try: + request = EstRequestController(est, include_elster_responses) + return request.process() + except EricProcessNotSuccessful as e: + logging.getLogger().info("Could not send est", exc_info=True) + raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) diff --git a/erica/api/v1/endpoints/rente/tax.py b/erica/api/v1/endpoints/rente/tax.py new file mode 100644 index 00000000..38647932 --- /dev/null +++ b/erica/api/v1/endpoints/rente/tax.py @@ -0,0 +1,31 @@ +import logging + +from fastapi import HTTPException, status, APIRouter +from starlette.responses import FileResponse +from erica.pyeric.eric_errors import EricProcessNotSuccessful +from erica.request_processing.requests_controller import CheckTaxNumberRequestController + +router = APIRouter() + + +@router.get('/tax_number_validity/{state_abbreviation}/{tax_number}', status_code=status.HTTP_200_OK) +def is_valid_tax_number(state_abbreviation: str, tax_number: str): + """ + Validates a tax number and returns the result + + :param state_abbreviation: Abbreviation of the state of the tax office + :param tax_number: Tax number in the standard schema + """ + try: + return CheckTaxNumberRequestController.process(state_abbreviation, tax_number) + except EricProcessNotSuccessful as e: + logging.getLogger().info("Could not validate tax number", exc_info=True) + raise HTTPException(status_code=422, detail=e.generate_error_response(include_responses=False)) + + +@router.get('/tax_offices/', status_code=status.HTTP_200_OK) +def get_tax_offices(): + """ + The list of tax offices for all states is requested and returned. + """ + return FileResponse("erica/static/tax_offices.json") diff --git a/erica/api/v1/endpoints/rente/unlock_code.py b/erica/api/v1/endpoints/rente/unlock_code.py new file mode 100644 index 00000000..db839b72 --- /dev/null +++ b/erica/api/v1/endpoints/rente/unlock_code.py @@ -0,0 +1,65 @@ +import logging + +from fastapi import HTTPException, status, APIRouter +from erica.request_processing.erica_input.v1.erica_input import UnlockCodeRequestData, UnlockCodeActivationData, \ + UnlockCodeRevocationData +from erica.pyeric.eric_errors import EricProcessNotSuccessful +from erica.request_processing.requests_controller import UnlockCodeRequestController, \ + UnlockCodeActivationRequestController, UnlockCodeRevocationRequestController + +router = APIRouter() + + +@router.post('/unlock_code_requests', status_code=status.HTTP_201_CREATED) +def request_unlock_code(unlock_code_request: UnlockCodeRequestData, include_elster_responses: bool = False): + """ + A new unlock code for the sent id_nr is requested. If everything is successful, return a 200 HTTP response + with {'elster_request_id': str, 'idnr': str}. If there is any error return a 400 repsonse for client + errors and a 500 response for server errors with {‘code’ : int, ‘message’: str,‘description’: str} + + :param unlock_code_request: the JSON input data for the request + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + try: + request = UnlockCodeRequestController(unlock_code_request, include_elster_responses) + return request.process() + except EricProcessNotSuccessful as e: + logging.getLogger().info("Could not request unlock code", exc_info=True) + raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) + + +@router.post('/unlock_code_activations', status_code=status.HTTP_201_CREATED) +def activate_unlock_code(unlock_code_activation: UnlockCodeActivationData, include_elster_responses: bool = False): + """ + An unlock code is used activated for the sent id_nr. If everything is successful, return a 200 HTTP response + with {'id_nr': str}. If there is any error return a 400 response for client + errors and a 500 response for server errors with {‘code’ : int,‘message’: str,‘description’: str}. + + :param unlock_code_activation: the JSON input data for the activation + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + try: + request = UnlockCodeActivationRequestController(unlock_code_activation, include_elster_responses) + return request.process() + except EricProcessNotSuccessful as e: + logging.getLogger().info("Could not activate unlock code", exc_info=True) + raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) + + +@router.post('/unlock_code_revocations', status_code=status.HTTP_200_OK) +def revoke_unlock_code(unlock_code_revocation: UnlockCodeRevocationData, include_elster_responses: bool = False): + """ + The permission at Elster is revoked. If everything is successful, return a 200 HTTP response + with {'id_nr': str}. If there is any error return a 400 response for client + errors and a 500 response for server errors with {‘code’ : int, ‘message’: str, ‘description’: str}. + Especially, an error is returned if there is no activated or not activated unlock_code for the corresponding idnr. + + :param unlock_code_revocation: the JSON input data for the revocation + :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned + """ + try: + request = UnlockCodeRevocationRequestController(unlock_code_revocation, include_elster_responses) + return request.process() + except EricProcessNotSuccessful as e: + logging.getLogger().info("Could not revoke unlock code", exc_info=True) + raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) diff --git a/erica/api/v2/api_v2.py b/erica/api/v2/api_v2.py new file mode 100644 index 00000000..e8d83911 --- /dev/null +++ b/erica/api/v2/api_v2.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter + +from erica.api.v2.endpoints import ping +from erica.api.v2.endpoints.rente import fsc, est, tax + + +api_router_02 = APIRouter() +api_router_02.prefix = '/v2' + +api_router_02.include_router(ping.router, prefix="/ping", tags=["Ping"]) +api_router_02.include_router(est.router, tags=["Steuererklärung"]) +api_router_02.include_router(tax.router, tags=["Finanzverwaltung"]) +api_router_02.include_router(fsc.router, prefix="/fsc", tags=["Freischaltcode"]) + + + diff --git a/erica/api/v2/endpoints/ping.py b/erica/api/v2/endpoints/ping.py new file mode 100644 index 00000000..aa2a434f --- /dev/null +++ b/erica/api/v2/endpoints/ping.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get('/') +def ping(): + """Simple route that can be used to check if the app has started. + """ + return 'pong' diff --git a/erica/api/v2/endpoints/rente/est.py b/erica/api/v2/endpoints/rente/est.py new file mode 100644 index 00000000..3e4a9eb7 --- /dev/null +++ b/erica/api/v2/endpoints/rente/est.py @@ -0,0 +1,64 @@ +import logging + +from fastapi import status, APIRouter +from starlette.responses import JSONResponse + +from erica.api.v2.responses.model import response_model_post_to_queue, response_model_get_est_validation_from_queue, \ + response_model_get_send_est_from_queue +from erica.pyeric.utils import generate_dummy_error_response +from erica.request_processing.erica_input.v2.erica_input import EstDataWithTtl + +router = APIRouter() + + +@router.post('/est_validations', status_code=status.HTTP_201_CREATED, responses=response_model_post_to_queue) +def validate_est(est_data_ttl: EstDataWithTtl): + """ + Route for validation of a tax declaration using the job queue. + :param est_data_ttl: payload with TTL, JSON input data for the ESt and declaration year. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not validate est", exc_info=True) + return JSONResponse(status_code=422, content=generate_dummy_error_response()) + + +@router.get('/est_validations/{request_id}', status_code=status.HTTP_200_OK, + responses=response_model_get_est_validation_from_queue) +def get_validate_est_job(request_id: str): + """ + Route for retrieving job status of a tax declaration validation from the queue. + :param request_id: the id of the job. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not retrieve status of job " + request_id, exc_info=True) + return JSONResponse(status_code=500, content=generate_dummy_error_response()) + + +@router.post('/ests', status_code=status.HTTP_201_CREATED, responses=response_model_post_to_queue) +def send_est(est_data_ttl: EstDataWithTtl): + """ + Route for sending a tax declaration using the job queue. + :param est_data_ttl: payload with TTL, JSON input data for the ESt and declaration year. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not send est", exc_info=True) + return JSONResponse(status_code=422, content=generate_dummy_error_response()) + + +@router.get('/ests/{request_id}', status_code=status.HTTP_200_OK, responses=response_model_get_send_est_from_queue) +def get_send_est_job(request_id: str): + """ + Route for retrieving job status of a sent tax declaration from the queue. + :param request_id: the id of the job. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not retrieve status of job " + request_id, exc_info=True) + return JSONResponse(status_code=500, content=generate_dummy_error_response()) diff --git a/erica/api/v2/endpoints/rente/fsc.py b/erica/api/v2/endpoints/rente/fsc.py new file mode 100644 index 00000000..74ccf08c --- /dev/null +++ b/erica/api/v2/endpoints/rente/fsc.py @@ -0,0 +1,97 @@ +import logging + +from fastapi import status, APIRouter +from starlette.responses import JSONResponse + +from erica.api.v2.responses.model import response_model_get_unlock_code_request_from_queue, \ + response_model_get_unlock_code_activation_from_queue, response_model_get_unlock_code_revocation_from_queue +from erica.pyeric.utils import generate_dummy_error_response +from erica.request_processing.erica_input.v2.erica_input import ErrorRequestQueue, FscRequestDataWithTtl, \ + FscActivationDataWithTtl, FscRevocationDataWithTtl + +router = APIRouter() + + +@router.post('/request', status_code=status.HTTP_201_CREATED, responses={422: {"model": ErrorRequestQueue}}) +def request_fsc(request_fsc_ttl: FscRequestDataWithTtl): + """ + Route for requesting a new fsc for the sent id_nr using the job queue. + :param request_fsc_ttl: payload with TTL and the JSON input data for the request. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not request unlock code", exc_info=True) + return JSONResponse(status_code=422, content=generate_dummy_error_response()) + + +@router.get('/request/{request_id}', status_code=status.HTTP_200_OK, + responses=response_model_get_unlock_code_request_from_queue) +def get_request_fsc_job(request_id: str): + """ + Route for retrieving job status from an fsc request from the queue. + :param request_id: the id of the job. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not retrieve status of (unlock code request) job " + request_id, exc_info=True) + return JSONResponse(status_code=500, content=generate_dummy_error_response()) + + +@router.post('/activation', status_code=status.HTTP_201_CREATED, responses={422: {"model": ErrorRequestQueue}}) +def activation_fsc( + activation_fsc_ttl: FscActivationDataWithTtl): + """ + Route for requesting activation of an fsc for the sent id_nr using the job queue. + :param activation_fsc_ttl: payload with TTL and the JSON input data for the activation. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not activate unlock code", exc_info=True) + return JSONResponse(status_code=422, content=generate_dummy_error_response()) + + +@router.get('/activation/{request_id}', status_code=status.HTTP_200_OK, + responses=response_model_get_unlock_code_activation_from_queue) +def get_fsc_activation_job(request_id: str): + """ + Route for retrieving job status from an fsc activation from the queue. + :param request_id: the id of the job. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not retrieve status of (unlock code activation) job " + request_id, + exc_info=True) + return JSONResponse(status_code=500, content=generate_dummy_error_response()) + + +@router.post('/revocation', status_code=status.HTTP_201_CREATED, responses={422: {"model": ErrorRequestQueue}}) +def fsc_revocation( + fsc_revocation_ttl: FscRevocationDataWithTtl): + """ + Route for requesting revocation of an fsc for the sent id_nr using the job queue. + :param fsc_revocation_ttl: payload with TTL and the JSON input data for the revocation. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not revoke unlock code", exc_info=True) + return JSONResponse(status_code=422, content=generate_dummy_error_response()) + + +@router.get('/revocation/{request_id}', status_code=status.HTTP_200_OK, + responses=response_model_get_unlock_code_revocation_from_queue) +def get_fsc_revocation_job(request_id: str): + """ + Route for retrieving job status from an fsc revocation from the queue. + :param request_id: the id of the job. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not retrieve status of (unlock code revocation) job " + request_id, + exc_info=True) + return JSONResponse(status_code=500, content=generate_dummy_error_response()) diff --git a/erica/api/v2/endpoints/rente/tax.py b/erica/api/v2/endpoints/rente/tax.py new file mode 100644 index 00000000..04b1628e --- /dev/null +++ b/erica/api/v2/endpoints/rente/tax.py @@ -0,0 +1,45 @@ +import logging + +from fastapi import status, APIRouter +from starlette.responses import FileResponse, JSONResponse + +from erica.api.v2.responses.model import response_model_get_tax_number_validity_from_queue +from erica.pyeric.utils import generate_dummy_error_response +from erica.request_processing.erica_input.v2.erica_input import TaxValidityWithTtl, ErrorRequestQueue + +router = APIRouter() + + +@router.post('/tax_number_validity', status_code=status.HTTP_201_CREATED, responses={422: {"model": ErrorRequestQueue}}) +def is_valid_tax_number(tax_validity_ttl: TaxValidityWithTtl): + """ + Route for validation of a tax number using the job queue. + :param tax_validity_ttl: payload with abbreviation of the state of the tax office and tax number in the standard schema. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not validate tax number", exc_info=True) + return JSONResponse(status_code=422, content=generate_dummy_error_response()) + + +@router.get('/tax_number_validity/{request_id}', status_code=status.HTTP_200_OK, + responses=response_model_get_tax_number_validity_from_queue) +def get_valid_tax_number_job(request_id: str): + """ + Route for retrieving job status of a tax number validity from the queue. + :param request_id: the id of the job. + """ + try: + raise NotImplementedError() + except NotImplementedError: + logging.getLogger().info("Could not retrieve status of job " + request_id, exc_info=True) + return JSONResponse(status_code=500, content=generate_dummy_error_response()) + + +@router.get('/tax_offices/', status_code=status.HTTP_200_OK) +def get_tax_offices(): + """ + The list of tax offices for all states is requested and returned. + """ + return FileResponse("erica/static/tax_offices.json") diff --git a/erica/api/v2/responses/model.py b/erica/api/v2/responses/model.py new file mode 100644 index 00000000..e54da9c3 --- /dev/null +++ b/erica/api/v2/responses/model.py @@ -0,0 +1,40 @@ +from erica.request_processing.erica_input.v2.erica_input import ErrorRequestQueue, SuccessResponseGetFromQueue, \ + SuccessResponseGetSendEstFromQueue, SuccessResponseGetTaxNumberValidityFromQueue, \ + SuccessResponseGetUnlockCodeRequestAndActivationFromQueue, SuccessResponseGetUnlockCodeRevocationFromQueue + +model_error_request_queue = {"model": ErrorRequestQueue, + "description": "Job status could not be retrieved from the queue."} + +response_model_post_to_queue = { + 201: {"description": "Job was successfully submitted to the queue and the job id was returned."}, + 422: {"model": ErrorRequestQueue, "description": "Job could not be submitted to the queue."}} + +response_model_get_est_validation_from_queue = { + 200: {"model": SuccessResponseGetFromQueue, + "description": "Job status of an est validation was successfully retrieved from the queue."}, + 500: model_error_request_queue} + +response_model_get_send_est_from_queue = { + 200: {"model": SuccessResponseGetSendEstFromQueue, + "description": "Job status of a sent est was successfully retrieved from the queue."}, + 500: model_error_request_queue} + +response_model_get_tax_number_validity_from_queue = { + 200: {"model": SuccessResponseGetTaxNumberValidityFromQueue, + "description": "Job status of a tax number validity was successfully retrieved from the queue."}, + 500: model_error_request_queue} + +response_model_get_unlock_code_request_from_queue = { + 200: {"model": SuccessResponseGetUnlockCodeRequestAndActivationFromQueue, + "description": "Job status of an unlock code request was successfully retrieved from the queue."}, + 500: model_error_request_queue} + +response_model_get_unlock_code_activation_from_queue = { + 200: {"model": SuccessResponseGetUnlockCodeRequestAndActivationFromQueue, + "description": "Job status of an unlock code activation was successfully retrieved from the queue."}, + 500: model_error_request_queue} + +response_model_get_unlock_code_revocation_from_queue = { + 200: {"model": SuccessResponseGetUnlockCodeRevocationFromQueue, + "description": "Job status of an unlock code revocation was successfully retrieved from the queue."}, + 500: model_error_request_queue} diff --git a/erica/pyeric/utils.py b/erica/pyeric/utils.py index ea406e4e..125b90cf 100644 --- a/erica/pyeric/utils.py +++ b/erica/pyeric/utils.py @@ -9,3 +9,10 @@ def pretty_xml_string(xml_string): dom_string = minidom.parseString(xml_string).toprettyxml(indent=" "*4) return '\n'.join([s for s in dom_string.splitlines() if s.strip()]) # remove empty lines + + +def generate_dummy_error_response(): + error_response = {"errorCode": -1, + "errorMessage": "API resource not yet implemented." + } + return error_response diff --git a/erica/request_processing/eric_mapper.py b/erica/request_processing/eric_mapper.py index 35983c7b..9154b1b6 100644 --- a/erica/request_processing/eric_mapper.py +++ b/erica/request_processing/eric_mapper.py @@ -1,16 +1,14 @@ from datetime import date -from decimal import Decimal -from typing import Optional, List +from typing import Optional from pydantic import BaseModel, root_validator, validator -from erica.request_processing.erica_input import AccountHolder, MetaDataEst +from erica.request_processing.erica_input.v1.erica_input import AccountHolder, Stmind STANDARD_DATE_FORMAT = '%d.%m.%Y' -class EstEricMapping(BaseModel): - +class EstEricMapping(Stmind): steuernummer: Optional[str] submission_without_tax_nr: Optional[bool] bufa_nr: Optional[str] @@ -64,32 +62,6 @@ class EstEricMapping(BaseModel): person_b_fahrtkostenpauschale_has_merkzeichen_bl_tbl_h_ag_pflegegrad: Optional[bool] person_b_fahrtkostenpauschale_has_merkzeichen_g_and_degree_70_degree_80: Optional[bool] - stmind_haushaltsnahe_entries: Optional[List[str]] - stmind_haushaltsnahe_summe: Optional[Decimal] - stmind_handwerker_entries: Optional[List[str]] - stmind_handwerker_summe: Optional[Decimal] - stmind_handwerker_lohn_etc_summe: Optional[Decimal] - - stmind_vorsorge_summe: Optional[Decimal] - stmind_spenden_inland: Optional[Decimal] - stmind_spenden_inland_parteien: Optional[Decimal] - stmind_religion_paid_summe: Optional[Decimal] - stmind_religion_reimbursed_summe: Optional[Decimal] - - stmind_krankheitskosten_summe: Optional[Decimal] - stmind_krankheitskosten_anspruch: Optional[Decimal] - stmind_pflegekosten_summe: Optional[Decimal] - stmind_pflegekosten_anspruch: Optional[Decimal] - stmind_beh_aufw_summe: Optional[Decimal] - stmind_beh_aufw_anspruch: Optional[Decimal] - stmind_bestattung_summe: Optional[Decimal] - stmind_bestattung_anspruch: Optional[Decimal] - stmind_aussergbela_sonst_summe: Optional[Decimal] - stmind_aussergbela_sonst_anspruch: Optional[Decimal] - - stmind_gem_haushalt_count: Optional[int] - stmind_gem_haushalt_entries: Optional[List[str]] - @root_validator(pre=True) def set_pauschbetrag_disability_degrees(cls, values): if values.get('person_a_requests_pauschbetrag'): @@ -103,10 +75,10 @@ def set_pauschbetrag_disability_degrees(cls, values): @root_validator(pre=True) def set_person_a_merkzeichen_bl_tbl_h_pflegegrad(cls, values): merkzeichen_values = [ - values.get('person_a_has_pflegegrad'), - values.get('person_a_has_merkzeichen_bl'), - values.get('person_a_has_merkzeichen_tbl'), - values.get('person_a_has_merkzeichen_h') + values.get('person_a_has_pflegegrad'), + values.get('person_a_has_merkzeichen_bl'), + values.get('person_a_has_merkzeichen_tbl'), + values.get('person_a_has_merkzeichen_h') ] if any(merkzeichen_values) and values.get('person_a_requests_pauschbetrag'): values['person_a_pauschbetrag_has_merkzeichen_bl_tbl_h_pflegegrad'] = True @@ -115,8 +87,8 @@ def set_person_a_merkzeichen_bl_tbl_h_pflegegrad(cls, values): @root_validator(pre=True) def set_person_a_merkzeichen_g_ag(cls, values): merkzeichen_values = [ - values.get('person_a_has_merkzeichen_g'), - values.get('person_a_has_merkzeichen_ag') + values.get('person_a_has_merkzeichen_g'), + values.get('person_a_has_merkzeichen_ag') ] if any(merkzeichen_values) and values.get('person_a_requests_pauschbetrag'): values['person_a_pauschbetrag_has_merkzeichen_g_ag'] = True @@ -135,17 +107,18 @@ def set_person_a_fahrtkostenpauschale(cls, values): if values.get('person_a_requests_fahrtkostenpauschale'): if any(merkzeichen_for_higher_fahrtkostenpauschale): values['person_a_fahrtkostenpauschale_has_merkzeichen_bl_tbl_h_ag_pflegegrad'] = True - elif values.get('person_a_disability_degree') >= 80 or (values.get('person_a_has_merkzeichen_g') and values.get('person_a_disability_degree') >= 70): + elif values.get('person_a_disability_degree') >= 80 or ( + values.get('person_a_has_merkzeichen_g') and values.get('person_a_disability_degree') >= 70): values['person_a_fahrtkostenpauschale_has_merkzeichen_g_and_degree_70_degree_80'] = True return values @root_validator(pre=True) - def set_person_b_merkzeichen_bl_tbl_h_pflegegrad(cls,values): + def set_person_b_merkzeichen_bl_tbl_h_pflegegrad(cls, values): merkzeichen_values = [ - values.get('person_b_has_pflegegrad'), - values.get('person_b_has_merkzeichen_bl'), - values.get('person_b_has_merkzeichen_tbl'), - values.get('person_b_has_merkzeichen_h') + values.get('person_b_has_pflegegrad'), + values.get('person_b_has_merkzeichen_bl'), + values.get('person_b_has_merkzeichen_tbl'), + values.get('person_b_has_merkzeichen_h') ] if any(merkzeichen_values) and values.get('person_b_requests_pauschbetrag'): values['person_b_pauschbetrag_has_merkzeichen_bl_tbl_h_pflegegrad'] = True @@ -154,8 +127,8 @@ def set_person_b_merkzeichen_bl_tbl_h_pflegegrad(cls,values): @root_validator(pre=True) def set_person_b_merkzeichen_g_ag(cls, values): merkzeichen_values = [ - values.get('person_b_has_merkzeichen_g'), - values.get('person_b_has_merkzeichen_ag') + values.get('person_b_has_merkzeichen_g'), + values.get('person_b_has_merkzeichen_ag') ] if any(merkzeichen_values) and values.get('person_b_requests_pauschbetrag'): values['person_b_pauschbetrag_has_merkzeichen_g_ag'] = True @@ -174,7 +147,8 @@ def set_person_b_fahrtkostenpauschale(cls, values): if values.get('person_b_requests_fahrtkostenpauschale'): if any(merkzeichen_for_higher_fahrtkostenpauschale): values['person_b_fahrtkostenpauschale_has_merkzeichen_bl_tbl_h_ag_pflegegrad'] = True - elif values.get('person_b_disability_degree') >= 80 or (values.get('person_b_has_merkzeichen_g') and values.get('person_b_disability_degree') >= 70): + elif values.get('person_b_disability_degree') >= 80 or ( + values.get('person_b_has_merkzeichen_g') and values.get('person_b_disability_degree') >= 70): values['person_b_fahrtkostenpauschale_has_merkzeichen_g_and_degree_70_degree_80'] = True return values @@ -193,4 +167,4 @@ class UnlockCodeRequestEricMapper(BaseModel): @validator('dob', pre=True) def convert_datetime_to_y_m_d(cls, v): - return v.strftime('%Y-%m-%d') if v else None \ No newline at end of file + return v.strftime('%Y-%m-%d') if v else None diff --git a/erica/request_processing/erica_input.py b/erica/request_processing/erica_input/v1/erica_input.py similarity index 98% rename from erica/request_processing/erica_input.py rename to erica/request_processing/erica_input/v1/erica_input.py index 6edd1e4d..14ac5d2d 100644 --- a/erica/request_processing/erica_input.py +++ b/erica/request_processing/erica_input/v1/erica_input.py @@ -3,7 +3,7 @@ from enum import Enum from typing import Optional, List -from pydantic import BaseModel, validator, root_validator +from pydantic import BaseModel, validator from erica.elster_xml.elster_xml_generator import VERANLAGUNGSJAHR from erica.elster_xml.est_validation import is_valid_bufa @@ -15,7 +15,36 @@ class AccountHolder(str, Enum): person_b = 'person_b' -class FormDataEst(BaseModel): +class Stmind(BaseModel): + + stmind_haushaltsnahe_entries: Optional[List[str]] + stmind_haushaltsnahe_summe: Optional[Decimal] + stmind_handwerker_entries: Optional[List[str]] + stmind_handwerker_summe: Optional[Decimal] + stmind_handwerker_lohn_etc_summe: Optional[Decimal] + + stmind_vorsorge_summe: Optional[Decimal] + stmind_spenden_inland: Optional[Decimal] + stmind_spenden_inland_parteien: Optional[Decimal] + stmind_religion_paid_summe: Optional[Decimal] + stmind_religion_reimbursed_summe: Optional[Decimal] + + stmind_krankheitskosten_summe: Optional[Decimal] + stmind_krankheitskosten_anspruch: Optional[Decimal] + stmind_pflegekosten_summe: Optional[Decimal] + stmind_pflegekosten_anspruch: Optional[Decimal] + stmind_beh_aufw_summe: Optional[Decimal] + stmind_beh_aufw_anspruch: Optional[Decimal] + stmind_bestattung_summe: Optional[Decimal] + stmind_bestattung_anspruch: Optional[Decimal] + stmind_aussergbela_sonst_summe: Optional[Decimal] + stmind_aussergbela_sonst_anspruch: Optional[Decimal] + + stmind_gem_haushalt_count: Optional[int] + stmind_gem_haushalt_entries: Optional[List[str]] + + +class FormDataEst(Stmind): steuernummer: Optional[str] submission_without_tax_nr: Optional[bool] bufa_nr: Optional[str] @@ -75,32 +104,6 @@ class FormDataEst(BaseModel): person_b_requests_pauschbetrag: Optional[bool] person_b_requests_fahrtkostenpauschale: Optional[bool] - stmind_haushaltsnahe_entries: Optional[List[str]] - stmind_haushaltsnahe_summe: Optional[Decimal] - stmind_handwerker_entries: Optional[List[str]] - stmind_handwerker_summe: Optional[Decimal] - stmind_handwerker_lohn_etc_summe: Optional[Decimal] - - stmind_vorsorge_summe: Optional[Decimal] - stmind_spenden_inland: Optional[Decimal] - stmind_spenden_inland_parteien: Optional[Decimal] - stmind_religion_paid_summe: Optional[Decimal] - stmind_religion_reimbursed_summe: Optional[Decimal] - - stmind_krankheitskosten_summe: Optional[Decimal] - stmind_krankheitskosten_anspruch: Optional[Decimal] - stmind_pflegekosten_summe: Optional[Decimal] - stmind_pflegekosten_anspruch: Optional[Decimal] - stmind_beh_aufw_summe: Optional[Decimal] - stmind_beh_aufw_anspruch: Optional[Decimal] - stmind_bestattung_summe: Optional[Decimal] - stmind_bestattung_anspruch: Optional[Decimal] - stmind_aussergbela_sonst_summe: Optional[Decimal] - stmind_aussergbela_sonst_anspruch: Optional[Decimal] - - stmind_gem_haushalt_count: Optional[int] - stmind_gem_haushalt_entries: Optional[List[str]] - @validator('submission_without_tax_nr', always=True) def if_no_tax_number_must_be_submission_without_tax_nr(cls, v, values): if values.get('steuernummer') and v: @@ -237,3 +240,6 @@ class UnlockCodeRevocationData(BaseModel): class GetAddressData(BaseModel): idnr: str + + + diff --git a/erica/request_processing/erica_input/v2/erica_input.py b/erica/request_processing/erica_input/v2/erica_input.py new file mode 100644 index 00000000..d8f51703 --- /dev/null +++ b/erica/request_processing/erica_input/v2/erica_input.py @@ -0,0 +1,87 @@ +from typing import Union +from pydantic import BaseModel +from erica.request_processing.erica_input.v1.erica_input import EstData, UnlockCodeRequestData, \ + UnlockCodeActivationData, UnlockCodeRevocationData +from enum import Enum + + +class EstDataWithTtl(BaseModel): + ttlInMinutes: int + payload: EstData + + +class TaxValidity(BaseModel): + state_abbreviation: str + tax_number: str + + +class TaxValidityWithTtl(BaseModel): + ttlInMinutes: int + payload: TaxValidity + + +class FscRequestDataWithTtl(BaseModel): + ttlInMinutes: int + payload: UnlockCodeRequestData + + +class FscActivationDataWithTtl(BaseModel): + ttlInMinutes: int + payload: UnlockCodeActivationData + + +class FscRevocationDataWithTtl(BaseModel): + ttlInMinutes: int + payload: UnlockCodeRevocationData + + +class ErrorRequestQueue(BaseModel): + errorCode: str + errorMessage: str + + +class Status(Enum): + PROCESSING = "Processing" + FAILURE = "Failure" + SUCCESS = "Success" + + +class SuccessResponseGetFromQueue(BaseModel): + processStatus: Status + result: Union[BaseModel, None] + errorCode: Union[str, None] + errorMessage: Union[str, None] + + +class ResultGetSendEstFromQueue(BaseModel): + transfer_ticket: str + pdf: str + + +class SuccessResponseGetSendEstFromQueue(SuccessResponseGetFromQueue): + result: ResultGetSendEstFromQueue + + +class ResultGetTaxNumberValidityFromQueue(BaseModel): + is_valid: bool + + +class SuccessResponseGetTaxNumberValidityFromQueue(SuccessResponseGetFromQueue): + result: ResultGetTaxNumberValidityFromQueue + + +class TransferTicketAndIdnr(BaseModel): + transfer_ticket: str + idnr: str + + +class ResultGetUnlockCodeRequestAndActivationFromQueue(TransferTicketAndIdnr): + elster_request_id: str + + +class SuccessResponseGetUnlockCodeRequestAndActivationFromQueue(SuccessResponseGetFromQueue): + result: ResultGetUnlockCodeRequestAndActivationFromQueue + + +class SuccessResponseGetUnlockCodeRevocationFromQueue(SuccessResponseGetFromQueue): + result: TransferTicketAndIdnr diff --git a/erica/request_processing/requests_controller.py b/erica/request_processing/requests_controller.py index bcdbe379..b30022ba 100644 --- a/erica/request_processing/requests_controller.py +++ b/erica/request_processing/requests_controller.py @@ -16,7 +16,7 @@ DecryptBelegePyericController, BelegIdRequestPyericProcessController, \ BelegRequestPyericProcessController, CheckTaxNumberPyericController from erica.request_processing.eric_mapper import EstEricMapping, UnlockCodeRequestEricMapper -from erica.request_processing.erica_input import UnlockCodeRequestData, EstData +from erica.request_processing.erica_input.v1.erica_input import UnlockCodeRequestData, EstData SPECIAL_TESTMERKER_IDNR = '04452397687' diff --git a/erica/routes.py b/erica/routes.py deleted file mode 100644 index 7fe526fd..00000000 --- a/erica/routes.py +++ /dev/null @@ -1,169 +0,0 @@ -import logging - -from fastapi import HTTPException, status -from starlette.responses import FileResponse - -from erica import app -from erica.request_processing.erica_input import EstData, UnlockCodeRequestData, UnlockCodeActivationData, \ - UnlockCodeRevocationData, GetAddressData, GrundsteuerData -from erica.pyeric.eric_errors import EricProcessNotSuccessful -from erica.request_processing.requests_controller import UnlockCodeRequestController, \ - UnlockCodeActivationRequestController, EstValidationRequestController, EstRequestController, \ - UnlockCodeRevocationRequestController, CheckTaxNumberRequestController - -ERICA_VERSION_URL = '/01' - - -@app.get(ERICA_VERSION_URL + '/est_validations') -def validate_est(est: EstData, include_elster_responses: bool = False): - """ - Data for a Est is validated using ERiC. If the validation is successful then this should return - a 200 HTTP response with {'success': bool, 'est': est}. Otherwise this should return a 400 response if the - validation failed with {‘code’ : int,‘message’: str,‘description’: str,‘‘validation_problems’ : [{‘code’: int, - ‘message’: str}]} or a 400 response for other client errors and a 500 response for server errors with {‘code’ : - int, ‘message’: str, ‘description’: str} - - :param est: the JSON input data for the ESt - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - try: - request = EstValidationRequestController(est, include_elster_responses) - return request.process() - except EricProcessNotSuccessful as e: - logging.getLogger().info("Could not validate est", exc_info=True) - raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) - - -@app.post(ERICA_VERSION_URL + '/ests', status_code=status.HTTP_201_CREATED) -def send_est(est: EstData, include_elster_responses: bool = False): - """ - An Est is validated and then send to ELSTER using ERiC. If it is successful, this should return a 200 HTTP - response with {'transfer_ticket': str, 'pdf': str}. The pdf is base64 encoded binary data of the pdf - If there is any error with the validation, this should return a 400 response. If the validation failed with - {‘code’ : int,‘message’: str,‘description’: str, ‘validation_problems’ : [{‘code’: int, ‘message’: str}]} - or a 400 repsonse for other client errors and a 500 response for server errors with - {‘code’ : int,‘message’: str,‘description’: str} - - :param est: the JSON input data for the ESt - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - try: - request = EstRequestController(est, include_elster_responses) - return request.process() - except EricProcessNotSuccessful as e: - logging.getLogger().info("Could not send est", exc_info=True) - raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) - - -@app.post(ERICA_VERSION_URL + '/grundsteuer', status_code=status.HTTP_201_CREATED) -def send_grundsteuer(grundsteuer_data: GrundsteuerData, include_elster_responses: bool = False): - """ - The Grundsteuer data is validated and then send to ELSTER using ERiC. If it is successful, this should return a 201 - HTTP response with {'transfer_ticket': str, 'pdf': str}. The pdf is base64 encoded binary data of the pdf - If there is any error with the validation, this should return a 400 response. If the validation failed with - {‘code’ : int,‘message’: str,‘description’: str, ‘validation_problems’ : [{‘code’: int, ‘message’: str}]} - or a 400 repsonse for other client errors and a 500 response for server errors with - {‘code’ : int,‘message’: str,‘description’: str} - - :param grundsteuer_data: the JSON input data for the land tax declaration - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - return {"request": "successful"} - - -@app.post(ERICA_VERSION_URL + '/unlock_code_requests', status_code=status.HTTP_201_CREATED) -def request_unlock_code(unlock_code_request: UnlockCodeRequestData, include_elster_responses: bool = False): - """ - A new unlock code for the sent id_nr is requested. If everything is successful, return a 200 HTTP response - with {'elster_request_id': str, 'idnr': str}. If there is any error return a 400 repsonse for client - errors and a 500 response for server errors with {‘code’ : int, ‘message’: str,‘description’: str} - - :param unlock_code_request: the JSON input data for the request - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - try: - request = UnlockCodeRequestController(unlock_code_request, include_elster_responses) - return request.process() - except EricProcessNotSuccessful as e: - logging.getLogger().info("Could not request unlock code", exc_info=True) - raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) - - -@app.post(ERICA_VERSION_URL + '/unlock_code_activations', status_code=status.HTTP_201_CREATED) -def activate_unlock_code(unlock_code_activation: UnlockCodeActivationData, include_elster_responses: bool = False): - """ - An unlock code is used activated for the sent id_nr. If everything is successful, return a 200 HTTP response - with {'id_nr': str}. If there is any error return a 400 response for client - errors and a 500 response for server errors with {‘code’ : int,‘message’: str,‘description’: str}. - - :param unlock_code_activation: the JSON input data for the activation - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - try: - request = UnlockCodeActivationRequestController(unlock_code_activation, include_elster_responses) - return request.process() - except EricProcessNotSuccessful as e: - logging.getLogger().info("Could not activate unlock code", exc_info=True) - raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) - - -@app.post(ERICA_VERSION_URL + '/unlock_code_revocations', status_code=status.HTTP_200_OK) -def revoke_unlock_code(unlock_code_revocation: UnlockCodeRevocationData, include_elster_responses: bool = False): - """ - The permission at Elster is revoked. If everything is successful, return a 200 HTTP response - with {'id_nr': str}. If there is any error return a 400 response for client - errors and a 500 response for server errors with {‘code’ : int, ‘message’: str, ‘description’: str}. - Especially, an error is returned if there is no activated or not activated unlock_code for the corresponding idnr. - - :param unlock_code_revocation: the JSON input data for the revocation - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - try: - request = UnlockCodeRevocationRequestController(unlock_code_revocation, include_elster_responses) - return request.process() - except EricProcessNotSuccessful as e: - logging.getLogger().info("Could not revoke unlock code", exc_info=True) - raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses)) - - -@app.get(ERICA_VERSION_URL + '/tax_number_validity/{state_abbreviation}/{tax_number}', status_code=status.HTTP_200_OK) -def is_valid_tax_number(state_abbreviation: str, tax_number: str): - """ - Validates a tax number and returns the result - - :param state_abbreviation: Abbreviation of the state of the tax office - :param tax_number: Tax number in the standard schema - """ - try: - return CheckTaxNumberRequestController.process(state_abbreviation, tax_number) - except EricProcessNotSuccessful as e: - logging.getLogger().info("Could not validate tax number", exc_info=True) - raise HTTPException(status_code=422, detail=e.generate_error_response(include_responses=False)) - - -@app.get(ERICA_VERSION_URL + '/tax_offices/', status_code=status.HTTP_200_OK) -def get_tax_offices(): - """ - The list of tax offices for all states is requested and returned. - """ - return FileResponse("erica/static/tax_offices.json") - - -@app.post(ERICA_VERSION_URL + '/address', status_code=status.HTTP_200_OK) -def get_address(get_address: GetAddressData, include_elster_responses: bool = False): - """ - The address data of the given idnr is requested at Elster and returned. Be aware, that you need a permission - (aka an activated unlock_code) to query a person's data. - - :param get_address: the JSON input data for the request - :param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned - """ - # For now, we do not allow data requests as we cannot guarantee that Elster already has the relevant data gathered - raise NotImplementedError() - - -@app.get(ERICA_VERSION_URL + '/ping') -def ping(): - """Simple route that can be used to check if the app has started. - """ - return 'pong' diff --git a/tests/request_processing/test_eric_mapper.py b/tests/request_processing/test_eric_mapper.py index 77ff1c63..93833f3d 100644 --- a/tests/request_processing/test_eric_mapper.py +++ b/tests/request_processing/test_eric_mapper.py @@ -1,12 +1,9 @@ from datetime import date -from decimal import Decimal -from typing import Optional import pytest -from pydantic.main import BaseModel from erica.request_processing.eric_mapper import EstEricMapping -from erica.request_processing.erica_input import FormDataEst +from erica.request_processing.erica_input.v1.erica_input import FormDataEst @pytest.fixture diff --git a/tests/request_processing/test_erica_input.py b/tests/request_processing/test_erica_input.py index 07584aa4..8a7b148d 100644 --- a/tests/request_processing/test_erica_input.py +++ b/tests/request_processing/test_erica_input.py @@ -1,4 +1,3 @@ -import unittest from datetime import date from decimal import Decimal from unittest.mock import MagicMock, patch @@ -7,7 +6,7 @@ from pydantic import ValidationError from erica.pyeric.eric_errors import InvalidBufaNumberError -from erica.request_processing.erica_input import FormDataEst, MetaDataEst +from erica.request_processing.erica_input.v1.erica_input import FormDataEst, MetaDataEst from tests.utils import TEST_EST_VERANLAGUNGSJAHR @@ -146,7 +145,7 @@ def test_if_not_valid_bufa_then_raise_exception(self, standard_est_data): standard_est_data['submission_without_tax_nr'] = True standard_est_data['bufa_nr'] = '1981' - with patch('erica.request_processing.erica_input.is_valid_bufa', MagicMock(return_value=False)): + with patch('erica.request_processing.erica_input.v1.erica_input.is_valid_bufa', MagicMock(return_value=False)): with pytest.raises(InvalidBufaNumberError): FormDataEst.parse_obj(standard_est_data) diff --git a/tests/request_processing/test_requests_controller.py b/tests/request_processing/test_requests_controller.py index 9e764dd7..ca73e3d0 100644 --- a/tests/request_processing/test_requests_controller.py +++ b/tests/request_processing/test_requests_controller.py @@ -7,7 +7,7 @@ from erica.pyeric.eric_errors import InvalidBufaNumberError from erica.pyeric.pyeric_response import PyericResponse from erica.request_processing.eric_mapper import EstEricMapping, UnlockCodeRequestEricMapper -from erica.request_processing.erica_input import UnlockCodeRequestData, UnlockCodeActivationData, \ +from erica.request_processing.erica_input.v1.erica_input import UnlockCodeRequestData, UnlockCodeActivationData, \ UnlockCodeRevocationData, GetAddressData from erica.request_processing.requests_controller import UnlockCodeRequestController, \ UnlockCodeActivationRequestController, EstRequestController, EstValidationRequestController, \ diff --git a/tests/test_routes.py b/tests/test_routes.py index 93c5a325..5a836770 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -5,11 +5,14 @@ import pytest from fastapi.exceptions import HTTPException +from erica.api.v1.endpoints.grundsteuer.grundsteuer import send_grundsteuer +from erica.api.v1.endpoints.rente.est import validate_est, send_est +from erica.api.v1.endpoints.rente.tax import is_valid_tax_number, get_tax_offices +from erica.api.v1.endpoints.rente.unlock_code import request_unlock_code, activate_unlock_code, revoke_unlock_code from erica.pyeric.eric import EricResponse from erica.pyeric.pyeric_controller import GetTaxOfficesPyericController -from erica.request_processing.erica_input import GrundsteuerData -from erica.routes import request_unlock_code, activate_unlock_code, send_est, validate_est, revoke_unlock_code, \ - get_tax_offices, is_valid_tax_number, send_grundsteuer +from erica.request_processing.erica_input.v1.erica_input import GrundsteuerData + from tests.utils import create_unlock_request, create_unlock_activation, create_est, create_unlock_revocation, \ missing_cert, missing_pyeric_lib @@ -159,7 +162,8 @@ def setUp(self): @pytest.mark.skipif(missing_cert(), reason="skipped because of missing cert.pfx; see pyeric/README.md") @pytest.mark.skipif(missing_pyeric_lib(), reason="skipped because of missing eric lib; see pyeric/README.md") - def test_if_request_correct_and_elster_request_id_incorrect_then_raise_httpexception_with_elster_transfer_error(self): + def test_if_request_correct_and_elster_request_id_incorrect_then_raise_httpexception_with_elster_transfer_error( + self): correct_activation_no_include = create_unlock_activation(correct=True) try: @@ -275,7 +279,7 @@ def test_get_tax_offices_returns_same_as_request_controller_process(self): response = get_tax_offices() with open(response.path, "r") as response_file: response_content = json.load(response_file) - + erica_response = GetTaxOfficesPyericController().get_eric_response() - + self.assertEqual(erica_response, response_content) diff --git a/tests/test_sample_data_validations.py b/tests/test_sample_data_validations.py index 18d889f1..54b2fa08 100644 --- a/tests/test_sample_data_validations.py +++ b/tests/test_sample_data_validations.py @@ -4,7 +4,7 @@ import pytest -from erica.routes import validate_est +from erica.api.v1.endpoints.rente.est import validate_est from tests.utils import create_est, create_est_single, missing_cert, missing_pyeric_lib diff --git a/tests/utils.py b/tests/utils.py index 405eb647..57cc71a3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,7 +6,7 @@ from xml.etree import ElementTree as ET from erica.config import get_settings -from erica.request_processing.erica_input import EstData, FormDataEst, MetaDataEst, UnlockCodeRequestData, \ +from erica.request_processing.erica_input.v1.erica_input import EstData, FormDataEst, MetaDataEst, UnlockCodeRequestData, \ UnlockCodeActivationData, UnlockCodeRevocationData from erica.elster_xml.elster_xml_generator import VERANLAGUNGSJAHR