apply new state handling to API functions

This commit is contained in:
Florian Förster 2025-03-05 15:50:42 +01:00
parent 7b82d051e2
commit b4b4181bd0
3 changed files with 72 additions and 74 deletions

View File

@ -2,32 +2,26 @@ from __future__ import annotations
import re import re
from datetime import datetime as Datetime from datetime import datetime as Datetime
from typing import Final, Never from typing import TYPE_CHECKING, Final, Never
import requests import requests
from pydantic import BaseModel, PositiveInt, SkipValidation from pydantic import BaseModel, PositiveInt, SkipValidation
from requests import Response from requests import Response
from delta_barth._management import STATE_HANDLER
from delta_barth.constants import HTTP_CURRENT_CONNECTION, KnownDelBarApiErrorCodes from delta_barth.constants import HTTP_CURRENT_CONNECTION, KnownDelBarApiErrorCodes
from delta_barth.errors import ( from delta_barth.errors import (
ApiConnectionError, ApiConnectionError,
UnknownApiErrorCode,
UnspecifiedRequestType, UnspecifiedRequestType,
) )
from delta_barth.types import DelBarApiError, HttpRequestTypes from delta_barth.types import DelBarApiError, HttpRequestTypes
if TYPE_CHECKING:
from delta_barth.types import Status
LOGIN_ERROR_CODES_KNOWN: Final[frozenset[int]] = frozenset((400, 401, 409, 500)) LOGIN_ERROR_CODES_KNOWN: Final[frozenset[int]] = frozenset((400, 401, 409, 500))
def _raise_for_unknown_error(
resp: Response,
) -> Never:
raise UnknownApiErrorCode(
f"Unknown response code for request. Status Code: {resp.status_code}, "
f"Content: {resp.text}"
)
def _assert_login_status() -> None: def _assert_login_status() -> None:
if not HTTP_CURRENT_CONNECTION.logged_in: if not HTTP_CURRENT_CONNECTION.logged_in:
raise ApiConnectionError("Curent session is not logged in") raise ApiConnectionError("Curent session is not logged in")
@ -73,7 +67,6 @@ class LoginRequest(BaseModel):
class LoginResponse(BaseModel): class LoginResponse(BaseModel):
token: str token: str
error: DelBarApiError | None = None
def login( def login(
@ -82,7 +75,7 @@ def login(
password: str, password: str,
database: str, database: str,
mandant: str, mandant: str,
) -> LoginResponse: ) -> tuple[LoginResponse, Status]:
ROUTE: Final[str] = "user/login" ROUTE: Final[str] = "user/login"
URL: Final = combine_route(base_url, ROUTE) URL: Final = combine_route(base_url, ROUTE)
@ -99,26 +92,23 @@ def login(
) )
response: LoginResponse response: LoginResponse
status: Status
if resp.status_code == 200: if resp.status_code == 200:
response = LoginResponse(**resp.json()) response = LoginResponse(**resp.json())
status = STATE_HANDLER.pipe_states.SUCCESS
HTTP_CURRENT_CONNECTION.add_session_token(response.token) HTTP_CURRENT_CONNECTION.add_session_token(response.token)
elif resp.status_code in KnownDelBarApiErrorCodes.COMMON.value: else:
response = LoginResponse(token="")
err = DelBarApiError(status_code=resp.status_code, **resp.json()) err = DelBarApiError(status_code=resp.status_code, **resp.json())
response = LoginResponse(token="", error=err) status = STATE_HANDLER.api_error(err)
else: # pragma: no cover
_raise_for_unknown_error(resp)
return response return response, status
# ** logout # ** logout
class LogoutResponse(BaseModel):
error: DelBarApiError | None = None
def logout( def logout(
base_url: str, base_url: str,
) -> LogoutResponse: ) -> tuple[None, Status]:
ROUTE: Final[str] = "user/logout" ROUTE: Final[str] = "user/logout"
URL: Final = combine_route(base_url, ROUTE) URL: Final = combine_route(base_url, ROUTE)
@ -127,17 +117,16 @@ def logout(
headers=HTTP_CURRENT_CONNECTION.headers, # type: ignore headers=HTTP_CURRENT_CONNECTION.headers, # type: ignore
) )
response: LogoutResponse response = None
status: Status
if resp.status_code == 200: if resp.status_code == 200:
response = LogoutResponse() status = STATE_HANDLER.SUCCESS
HTTP_CURRENT_CONNECTION.remove_session_token() HTTP_CURRENT_CONNECTION.remove_session_token()
elif resp.status_code in KnownDelBarApiErrorCodes.COMMON.value: else:
err = DelBarApiError(status_code=resp.status_code, **resp.json()) err = DelBarApiError(status_code=resp.status_code, **resp.json())
response = LogoutResponse(error=err) status = STATE_HANDLER.api_error(err)
else: # pragma: no cover
_raise_for_unknown_error(resp)
return response return response, status
# ** sales data # ** sales data
@ -156,14 +145,14 @@ class SalesPrognosisResponseEntry(BaseModel):
class SalesPrognosisResponse(BaseModel): class SalesPrognosisResponse(BaseModel):
daten: tuple[SalesPrognosisResponseEntry, ...] daten: tuple[SalesPrognosisResponseEntry, ...]
error: DelBarApiError | None = None # error: DelBarApiError | None = None
def get_sales_prognosis_data( def get_sales_prognosis_data(
base_url: str, base_url: str,
company_id: int | None = None, company_id: int | None = None,
start_date: Datetime | None = None, start_date: Datetime | None = None,
) -> SalesPrognosisResponse: ) -> tuple[SalesPrognosisResponse, Status]:
_assert_login_status() _assert_login_status()
ROUTE: Final[str] = "verkauf/umsatzprognosedaten" ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
URL: Final = combine_route(base_url, ROUTE) URL: Final = combine_route(base_url, ROUTE)
@ -179,12 +168,19 @@ def get_sales_prognosis_data(
) )
response: SalesPrognosisResponse response: SalesPrognosisResponse
status: Status
if resp.status_code == 200: if resp.status_code == 200:
response = SalesPrognosisResponse(**resp.json()) response = SalesPrognosisResponse(**resp.json())
elif resp.status_code in KnownDelBarApiErrorCodes.COMMON.value: # pragma: no cover status = STATE_HANDLER.SUCCESS
else:
response = SalesPrognosisResponse(daten=tuple())
err = DelBarApiError(status_code=resp.status_code, **resp.json()) err = DelBarApiError(status_code=resp.status_code, **resp.json())
response = SalesPrognosisResponse(daten=tuple(), error=err) status = STATE_HANDLER.api_error(err)
else: # pragma: no cover
_raise_for_unknown_error(resp)
return response # elif resp.status_code in KnownDelBarApiErrorCodes.COMMON.value: # pragma: no cover
# err = DelBarApiError(status_code=resp.status_code, **resp.json())
# response = SalesPrognosisResponse(daten=tuple(), error=err)
# else: # pragma: no cover
# _raise_for_unknown_error(resp)
return response, status

View File

@ -13,10 +13,6 @@ class UnspecifiedRequestType(Exception):
"""exception raised if for a given API endpoint a not defined operation is requested""" """exception raised if for a given API endpoint a not defined operation is requested"""
class UnknownApiErrorCode(Exception):
"""exception raised if for a given request a unknown error response code is transmitted"""
class ApiConnectionError(Exception): class ApiConnectionError(Exception):
"""exception raised if an established connection is needed, but the current session is not connected""" """exception raised if an established connection is needed, but the current session is not connected"""

View File

@ -3,10 +3,9 @@ from datetime import datetime as Datetime
import pytest import pytest
from delta_barth.api import common from delta_barth.api import common
from delta_barth.constants import HTTP_CURRENT_CONNECTION from delta_barth.constants import DEFAULT_API_ERR_CODE, HTTP_CURRENT_CONNECTION
from delta_barth.errors import ( from delta_barth.errors import (
ApiConnectionError, ApiConnectionError,
UnknownApiErrorCode,
UnspecifiedRequestType, UnspecifiedRequestType,
) )
from delta_barth.types import HttpRequestTypes from delta_barth.types import HttpRequestTypes
@ -68,46 +67,43 @@ def test_ping(api_base_url):
resp = common.ping(api_base_url, HttpRequestTypes.POST) resp = common.ping(api_base_url, HttpRequestTypes.POST)
@pytest.mark.api_con_required
def test_raise_unknown_error(api_base_url):
resp = common.ping(api_base_url, HttpRequestTypes.GET)
with pytest.raises(UnknownApiErrorCode):
common._raise_for_unknown_error(resp)
@pytest.mark.api_con_required @pytest.mark.api_con_required
def test_login_logout(credentials, api_base_url): def test_login_logout(credentials, api_base_url):
assert HTTP_CURRENT_CONNECTION.session_token is None assert HTTP_CURRENT_CONNECTION.session_token is None
resp = common.login( resp, state = common.login(
base_url=api_base_url, base_url=api_base_url,
user_name=credentials["user"], user_name=credentials["user"],
password=credentials["pwd"], password=credentials["pwd"],
database=credentials["db"], database=credentials["db"],
mandant=credentials["mandant"], mandant=credentials["mandant"],
) )
assert resp.error is None assert state.code == 0
assert HTTP_CURRENT_CONNECTION.session_token is not None assert HTTP_CURRENT_CONNECTION.session_token is not None
resp = common.logout( resp, state = common.logout(
base_url=api_base_url, base_url=api_base_url,
) )
assert resp.error is None assert resp is None
assert state.code == 0
assert HTTP_CURRENT_CONNECTION.session_token is None assert HTTP_CURRENT_CONNECTION.session_token is None
assert "DelecoToken" not in HTTP_CURRENT_CONNECTION.headers assert "DelecoToken" not in HTTP_CURRENT_CONNECTION.headers
resp = common.login(
resp, state = common.login(
base_url=api_base_url, base_url=api_base_url,
user_name=credentials["user"], user_name=credentials["user"],
password="WRONG_PASSWORD", password="WRONG_PASSWORD",
database=credentials["db"], database=credentials["db"],
mandant=credentials["mandant"], mandant=credentials["mandant"],
) )
assert resp.error is not None assert resp is not None
assert resp.error.status_code == 409 assert state.code == DEFAULT_API_ERR_CODE
assert resp.error.message == "Nutzer oder Passwort falsch." assert state.api_server_error is not None
assert state.api_server_error.status_code == 409
assert state.api_server_error.message == "Nutzer oder Passwort falsch."
@pytest.mark.api_con_required @pytest.mark.api_con_required
def test_get_sales_prognosis_data(credentials, api_base_url): def test_get_sales_prognosis_data(credentials, api_base_url):
resp = common.login( resp, state = common.login(
base_url=api_base_url, base_url=api_base_url,
user_name=credentials["user"], user_name=credentials["user"],
password=credentials["pwd"], password=credentials["pwd"],
@ -115,35 +111,45 @@ def test_get_sales_prognosis_data(credentials, api_base_url):
mandant=credentials["mandant"], mandant=credentials["mandant"],
) )
# test without company ID # test without company ID
assert resp.error is None assert state.code == 0
date = Datetime(2022, 6, 1) date = Datetime(2022, 6, 1)
resp = common.get_sales_prognosis_data(api_base_url, None, date) resp, state = common.get_sales_prognosis_data(api_base_url, None, date)
assert resp.error is None assert state.code == 0
assert len(resp.daten) > 0 assert len(resp.daten) > 0
date = Datetime(2030, 1, 1) date = Datetime(2030, 1, 1)
resp = common.get_sales_prognosis_data(api_base_url, None, date) resp, state = common.get_sales_prognosis_data(api_base_url, None, date)
assert resp.error is None assert state.code == 0
assert len(resp.daten) == 0 assert len(resp.daten) == 0
# test with company ID # test with company ID
assert resp.error is None assert state.code == 0
date = Datetime(2022, 6, 1) date = Datetime(2022, 6, 1)
company_id = 1024 company_id = 1024
resp = common.get_sales_prognosis_data(api_base_url, company_id, date) resp, state = common.get_sales_prognosis_data(api_base_url, company_id, date)
assert resp.error is None assert state.code == 0
assert len(resp.daten) > 0 assert len(resp.daten) > 0
date = Datetime(2030, 1, 1) date = Datetime(2030, 1, 1)
resp = common.get_sales_prognosis_data(api_base_url, company_id, date) resp, state = common.get_sales_prognosis_data(api_base_url, company_id, date)
assert resp.error is None assert state.code == 0
assert len(resp.daten) == 0 assert len(resp.daten) == 0
# test with non-existent company ID # test with non-existent company ID
assert resp.error is None assert state.code == 0
date = Datetime(2022, 6, 1) date = Datetime(2022, 6, 1)
company_id = 1000024 company_id = 1000024
resp = common.get_sales_prognosis_data(api_base_url, company_id, date) resp, state = common.get_sales_prognosis_data(api_base_url, company_id, date)
assert resp.error is None # TODO check if this behaviour is still considered "successful"
assert state.code == 0
assert len(resp.daten) == 0 assert len(resp.daten) == 0
# test without date
company_id = 1024
resp, state = common.get_sales_prognosis_data(api_base_url, company_id, None)
assert state.code == 0
assert len(resp.daten) > 0
# test without filters
resp, state = common.get_sales_prognosis_data(api_base_url, None, None)
assert state.code == 0
assert len(resp.daten) > 0
# close connection # close connection
resp = common.logout( resp, state = common.logout(
base_url=api_base_url, base_url=api_base_url,
) )
assert resp.error is None assert state.code == 0