Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ce5b74fa4 | |||
| e57d39c416 | |||
| 77b4bd9700 | |||
| 690431472c | |||
| 248b811786 | |||
| 453490c0f5 | |||
| 1d63469be9 | |||
| 67406b5690 | |||
| daaf48f1db | |||
| d754a94f98 |
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "delta-barth"
|
name = "delta-barth"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
description = "workflows and pipelines for the Python-based Plugin of Delta Barth's ERP system"
|
description = "workflows and pipelines for the Python-based Plugin of Delta Barth's ERP system"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Florian Förster", email = "f.foerster@d-opt.com"},
|
{name = "Florian Förster", email = "f.foerster@d-opt.com"},
|
||||||
@@ -74,7 +74,7 @@ directory = "reports/coverage"
|
|||||||
|
|
||||||
|
|
||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.5.8"
|
current_version = "0.5.9"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
@@ -9,16 +9,10 @@ from datetime import datetime as Datetime
|
|||||||
from typing import TYPE_CHECKING, Final, TypeAlias, cast
|
from typing import TYPE_CHECKING, Final, TypeAlias, cast
|
||||||
|
|
||||||
import joblib
|
import joblib
|
||||||
import joblib.externals
|
|
||||||
import joblib.externals.loky
|
|
||||||
import joblib.externals.loky.backend
|
|
||||||
import joblib.externals.loky.backend.popen_loky_win32
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import scipy.stats
|
import scipy.stats
|
||||||
import sqlalchemy as sql
|
import sqlalchemy as sql
|
||||||
|
|
||||||
# --- new: for calculating timedelta
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from sklearn.metrics import mean_absolute_error, r2_score
|
from sklearn.metrics import mean_absolute_error, r2_score
|
||||||
from sklearn.model_selection import KFold, RandomizedSearchCV
|
from sklearn.model_selection import KFold, RandomizedSearchCV
|
||||||
@@ -409,13 +403,13 @@ def _export_on_fail(
|
|||||||
|
|
||||||
def pipeline_sales_forecast(
|
def pipeline_sales_forecast(
|
||||||
session: Session,
|
session: Session,
|
||||||
company_id: int | None = None,
|
company_ids: list[int] | None = None,
|
||||||
start_date: Datetime | None = None,
|
start_date: Datetime | None = None,
|
||||||
) -> SalesPrognosisResultsExport:
|
) -> SalesPrognosisResultsExport:
|
||||||
logger_pipelines.info("[PIPELINES] Starting main sales forecast pipeline...")
|
logger_pipelines.info("[PIPELINES] Starting main sales forecast pipeline...")
|
||||||
response, status = get_sales_prognosis_data(
|
response, status = get_sales_prognosis_data(
|
||||||
session,
|
session,
|
||||||
company_id=company_id,
|
company_ids=company_ids,
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
)
|
)
|
||||||
if status != STATUS_HANDLER.SUCCESS:
|
if status != STATUS_HANDLER.SUCCESS:
|
||||||
|
|||||||
@@ -7,18 +7,20 @@ import requests
|
|||||||
from dopt_basics.io import combine_route
|
from dopt_basics.io import combine_route
|
||||||
from pydantic import BaseModel, PositiveInt, SkipValidation
|
from pydantic import BaseModel, PositiveInt, SkipValidation
|
||||||
|
|
||||||
from delta_barth.constants import API_CON_TIMEOUT
|
from delta_barth.constants import API_CON_TIMEOUT, MAX_LOGIN_RETRIES
|
||||||
from delta_barth.errors import STATUS_HANDLER
|
from delta_barth.errors import STATUS_HANDLER
|
||||||
from delta_barth.types import DelBarApiError, ExportResponse, ResponseType, Status
|
from delta_barth.types import DelBarApiError, ExportResponse, ResponseType, Status
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
from delta_barth.session import Session
|
from delta_barth.session import Session
|
||||||
|
|
||||||
|
|
||||||
# ** sales data
|
# ** sales data
|
||||||
# ** import
|
# ** import
|
||||||
class SalesPrognosisRequestP(BaseModel):
|
class SalesPrognosisRequestP(BaseModel):
|
||||||
FirmaId: SkipValidation[int | None]
|
FirmaIds: SkipValidation[list[int] | None]
|
||||||
BuchungsDatum: SkipValidation[Datetime | None]
|
BuchungsDatum: SkipValidation[Datetime | None]
|
||||||
|
|
||||||
|
|
||||||
@@ -53,29 +55,37 @@ class SalesPrognosisResultsExport(ExportResponse):
|
|||||||
|
|
||||||
def get_sales_prognosis_data(
|
def get_sales_prognosis_data(
|
||||||
session: Session,
|
session: Session,
|
||||||
company_id: int | None = None,
|
company_ids: list[int] | None = None,
|
||||||
start_date: Datetime | None = None,
|
start_date: Datetime | None = None,
|
||||||
) -> tuple[SalesPrognosisResponse, Status]:
|
) -> tuple[SalesPrognosisResponse, Status]:
|
||||||
_, status = session.assert_login()
|
|
||||||
if status != STATUS_HANDLER.SUCCESS:
|
|
||||||
response = SalesPrognosisResponse(daten=tuple())
|
|
||||||
return response, status
|
|
||||||
|
|
||||||
ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
|
ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
|
||||||
URL: Final = combine_route(session.base_url, ROUTE)
|
URL: Final = combine_route(session.base_url, ROUTE)
|
||||||
|
|
||||||
sales_prog_req = SalesPrognosisRequestP(
|
sales_prog_req = SalesPrognosisRequestP(
|
||||||
FirmaId=company_id,
|
FirmaIds=company_ids,
|
||||||
BuchungsDatum=start_date,
|
BuchungsDatum=start_date,
|
||||||
)
|
)
|
||||||
empty_response = SalesPrognosisResponse(daten=tuple())
|
empty_response = SalesPrognosisResponse(daten=tuple())
|
||||||
|
if not session.logged_in:
|
||||||
|
_, status = session.login()
|
||||||
|
if status != STATUS_HANDLER.SUCCESS:
|
||||||
|
return empty_response, status
|
||||||
|
|
||||||
|
resp: Response | None = None
|
||||||
try:
|
try:
|
||||||
resp = requests.get(
|
for attempt in range(1, (MAX_LOGIN_RETRIES + 1)):
|
||||||
URL,
|
resp = requests.get(
|
||||||
params=sales_prog_req.model_dump(mode="json", exclude_none=True),
|
URL,
|
||||||
headers=session.headers, # type: ignore[argumentType]
|
params=sales_prog_req.model_dump(mode="json", exclude_none=True),
|
||||||
timeout=API_CON_TIMEOUT,
|
headers=session.headers, # type: ignore[argumentType]
|
||||||
)
|
timeout=API_CON_TIMEOUT,
|
||||||
|
)
|
||||||
|
if resp.status_code == 401:
|
||||||
|
_, status = session.relogin()
|
||||||
|
if status != STATUS_HANDLER.SUCCESS and attempt == MAX_LOGIN_RETRIES:
|
||||||
|
return empty_response, status
|
||||||
|
continue
|
||||||
|
break
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
@@ -83,6 +93,7 @@ def get_sales_prognosis_data(
|
|||||||
|
|
||||||
response: SalesPrognosisResponse
|
response: SalesPrognosisResponse
|
||||||
status: Status
|
status: Status
|
||||||
|
assert resp is not None, "tried to use not defined response"
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
response = SalesPrognosisResponse(**resp.json())
|
response = SalesPrognosisResponse(**resp.json())
|
||||||
status = STATUS_HANDLER.SUCCESS
|
status = STATUS_HANDLER.SUCCESS
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ DEPLOYMENT_STATUS: Final[bool] = deployment_status
|
|||||||
|
|
||||||
|
|
||||||
# ** databases
|
# ** databases
|
||||||
DB_ECHO: Final[bool] = True
|
DB_ECHO: Final[bool] = False
|
||||||
|
|
||||||
# ** error handling
|
# ** error handling
|
||||||
DEFAULT_INTERNAL_ERR_CODE: Final[int] = 100
|
DEFAULT_INTERNAL_ERR_CODE: Final[int] = 100
|
||||||
@@ -50,7 +50,9 @@ class KnownDelBarApiErrorCodes(enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
# ** API
|
# ** API
|
||||||
API_CON_TIMEOUT: Final[float] = 10.0 # secs to response
|
API_CON_TIMEOUT: Final[float] = 20.0 # secs to response
|
||||||
|
MAX_LOGIN_RETRIES: Final[int] = 2
|
||||||
|
|
||||||
# ** API response parsing
|
# ** API response parsing
|
||||||
# ** column mapping [API-Response --> Target-Features]
|
# ** column mapping [API-Response --> Target-Features]
|
||||||
COL_MAP_SALES_PROGNOSIS: Final[DualDict[str, str]] = DualDict(
|
COL_MAP_SALES_PROGNOSIS: Final[DualDict[str, str]] = DualDict(
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ def _write_performance_metrics_wrapped(
|
|||||||
|
|
||||||
|
|
||||||
def pipeline_sales_forecast(
|
def pipeline_sales_forecast(
|
||||||
company_id: int | None,
|
company_ids: list[int] | None,
|
||||||
start_date: Datetime | None,
|
start_date: Datetime | None,
|
||||||
) -> JsonExportResponse:
|
) -> JsonExportResponse:
|
||||||
PIPELINE_NAME: Final[str] = "sales_forecast"
|
PIPELINE_NAME: Final[str] = "sales_forecast"
|
||||||
logger.info("[EXT-CALL PIPELINES] Starting main sales forecast pipeline...")
|
logger.info("[EXT-CALL PIPELINES] Starting main sales forecast pipeline...")
|
||||||
t_start = time.perf_counter_ns()
|
t_start = time.perf_counter_ns()
|
||||||
result = forecast.pipeline_sales_forecast(
|
result = forecast.pipeline_sales_forecast(
|
||||||
SESSION, company_id=company_id, start_date=start_date
|
SESSION, company_ids=company_ids, start_date=start_date
|
||||||
)
|
)
|
||||||
export = JsonExportResponse(result.model_dump_json())
|
export = JsonExportResponse(result.model_dump_json())
|
||||||
t_end = time.perf_counter_ns()
|
t_end = time.perf_counter_ns()
|
||||||
|
|||||||
@@ -292,44 +292,11 @@ class Session:
|
|||||||
|
|
||||||
return None, status
|
return None, status
|
||||||
|
|
||||||
def assert_login(
|
def relogin(
|
||||||
self,
|
self,
|
||||||
) -> tuple[LoginResponse, Status]:
|
) -> tuple[LoginResponse, Status]:
|
||||||
# check if login token is still valid
|
|
||||||
# re-login if necessary
|
|
||||||
if self.session_token is None:
|
if self.session_token is None:
|
||||||
return self.login()
|
return self.login()
|
||||||
|
|
||||||
# use known endpoint which requires a valid token in its header
|
self._remove_session_token()
|
||||||
# evaluate the response to decide if:
|
return self.login()
|
||||||
# current token is still valid, token is not valid, other errors occurred
|
|
||||||
ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
|
|
||||||
URL: Final = combine_route(self.base_url, ROUTE)
|
|
||||||
params: dict[str, int] = {"FirmaId": 999999}
|
|
||||||
empty_response = LoginResponse(token="")
|
|
||||||
try:
|
|
||||||
resp = requests.get(
|
|
||||||
URL,
|
|
||||||
params=params,
|
|
||||||
headers=self.headers, # type: ignore
|
|
||||||
timeout=API_CON_TIMEOUT,
|
|
||||||
)
|
|
||||||
except requests.exceptions.Timeout: # pragma: no cover
|
|
||||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
|
||||||
except requests.exceptions.RequestException: # pragma: no cover
|
|
||||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_ERROR
|
|
||||||
|
|
||||||
response: LoginResponse
|
|
||||||
status: Status
|
|
||||||
if resp.status_code == 200:
|
|
||||||
response = LoginResponse(token=self.session_token)
|
|
||||||
status = STATUS_HANDLER.SUCCESS
|
|
||||||
elif resp.status_code == 401:
|
|
||||||
self._remove_session_token()
|
|
||||||
response, status = self.login()
|
|
||||||
else:
|
|
||||||
response = empty_response
|
|
||||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
|
||||||
status = STATUS_HANDLER.api_error(err)
|
|
||||||
|
|
||||||
return response, status
|
|
||||||
|
|||||||
@@ -432,6 +432,8 @@ def test_export_on_fail():
|
|||||||
|
|
||||||
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
||||||
def test_pipeline_sales_forecast_SuccessDbWrite(exmpl_api_sales_prognosis_resp, session):
|
def test_pipeline_sales_forecast_SuccessDbWrite(exmpl_api_sales_prognosis_resp, session):
|
||||||
|
date = Datetime(2023, 8, 15)
|
||||||
|
company_ids = [5661, 1027, 1024]
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"delta_barth.analysis.forecast.get_sales_prognosis_data",
|
"delta_barth.analysis.forecast.get_sales_prognosis_data",
|
||||||
@@ -440,7 +442,7 @@ def test_pipeline_sales_forecast_SuccessDbWrite(exmpl_api_sales_prognosis_resp,
|
|||||||
):
|
):
|
||||||
get_mock.return_value = exmpl_api_sales_prognosis_resp, STATUS_HANDLER.SUCCESS
|
get_mock.return_value = exmpl_api_sales_prognosis_resp, STATUS_HANDLER.SUCCESS
|
||||||
sess_mock.cfg.forecast.threshold_month_data_points = 1
|
sess_mock.cfg.forecast.threshold_month_data_points = 1
|
||||||
result = fc.pipeline_sales_forecast(None) # type: ignore
|
result = fc.pipeline_sales_forecast(None, company_ids, date) # type: ignore
|
||||||
assert result.status == STATUS_HANDLER.SUCCESS
|
assert result.status == STATUS_HANDLER.SUCCESS
|
||||||
assert len(result.response.daten) > 0
|
assert len(result.response.daten) > 0
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,13 @@ import pytest
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from delta_barth.api import requests as requests_
|
from delta_barth.api import requests as requests_
|
||||||
from delta_barth.api.common import LoginResponse
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
@pytest.mark.api_con_required
|
||||||
def test_get_sales_prognosis_data_Success(session):
|
def test_get_sales_prognosis_data_Success(session):
|
||||||
resp, status = session.login()
|
# do not login: let routine do it
|
||||||
# test without company ID
|
# test without company ID
|
||||||
assert status.code == 0
|
date = Datetime(2023, 12, 15)
|
||||||
date = Datetime(2022, 6, 1)
|
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, None, date)
|
resp, status = requests_.get_sales_prognosis_data(session, None, date)
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
assert len(resp.daten) > 0
|
assert len(resp.daten) > 0
|
||||||
@@ -22,26 +20,25 @@ def test_get_sales_prognosis_data_Success(session):
|
|||||||
assert len(resp.daten) == 0
|
assert len(resp.daten) == 0
|
||||||
# test with company ID
|
# test with company ID
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
date = Datetime(2022, 6, 1)
|
date = Datetime(2023, 8, 15)
|
||||||
company_id = 1024
|
company_ids = [5661, 1027]
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, company_id, date)
|
resp, status = requests_.get_sales_prognosis_data(session, company_ids, date)
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
assert len(resp.daten) > 0
|
assert len(resp.daten) > 0
|
||||||
date = Datetime(2030, 1, 1)
|
date = Datetime(2030, 1, 1)
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, company_id, date)
|
resp, status = requests_.get_sales_prognosis_data(session, company_ids, date)
|
||||||
assert status.code == 0
|
assert status.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 status.code == 0
|
assert status.code == 0
|
||||||
date = Datetime(2022, 6, 1)
|
date = Datetime(2022, 6, 1)
|
||||||
company_id = 1000024
|
company_ids = [1000024]
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, company_id, date)
|
resp, status = requests_.get_sales_prognosis_data(session, company_ids, date)
|
||||||
# TODO check if this behaviour is still considered "successful"
|
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
assert len(resp.daten) == 0
|
assert len(resp.daten) == 0
|
||||||
# test without date
|
# test without date
|
||||||
company_id = 1024
|
company_ids = [1024]
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, company_id, None)
|
resp, status = requests_.get_sales_prognosis_data(session, company_ids, None)
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
assert len(resp.daten) > 0
|
assert len(resp.daten) > 0
|
||||||
# test without filters
|
# test without filters
|
||||||
@@ -54,12 +51,11 @@ def test_get_sales_prognosis_data_Success(session):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
@pytest.mark.api_con_required
|
||||||
def test_get_sales_prognosis_data_FailLogin(session, mock_get):
|
def test_get_sales_prognosis_data_NoAuth(session, mock_get):
|
||||||
session.login()
|
code = 401
|
||||||
code = 500
|
|
||||||
json = {
|
json = {
|
||||||
"message": "ServerError",
|
"message": "ServerError",
|
||||||
"code": "TestExternalServerError",
|
"code": "TestFailAuth",
|
||||||
"hints": "TestCase",
|
"hints": "TestCase",
|
||||||
}
|
}
|
||||||
mock_get.return_value.status_code = code
|
mock_get.return_value.status_code = code
|
||||||
@@ -76,6 +72,36 @@ def test_get_sales_prognosis_data_FailLogin(session, mock_get):
|
|||||||
assert status.api_server_error.hints == json["hints"]
|
assert status.api_server_error.hints == json["hints"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_sales_prognosis_data_FailLogin(session, mock_get, mock_put):
|
||||||
|
code = 401
|
||||||
|
json = {
|
||||||
|
"message": "ServerError",
|
||||||
|
"code": "TestFailAuth",
|
||||||
|
"hints": "TestCase",
|
||||||
|
}
|
||||||
|
mock_get.return_value.status_code = code
|
||||||
|
mock_get.return_value.json.return_value = json
|
||||||
|
|
||||||
|
code_put = 500
|
||||||
|
json_put = {
|
||||||
|
"message": "ServerError",
|
||||||
|
"code": "TestUnknownError",
|
||||||
|
"hints": "TestCase",
|
||||||
|
}
|
||||||
|
mock_put.return_value.status_code = code_put
|
||||||
|
mock_put.return_value.json.return_value = json_put
|
||||||
|
|
||||||
|
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
||||||
|
assert resp is not None
|
||||||
|
assert len(resp.daten) == 0
|
||||||
|
assert status.code == 400
|
||||||
|
assert status.api_server_error is not None
|
||||||
|
assert status.api_server_error.status_code == code_put
|
||||||
|
assert status.api_server_error.message == json_put["message"]
|
||||||
|
assert status.api_server_error.code == json_put["code"]
|
||||||
|
assert status.api_server_error.hints == json_put["hints"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
@pytest.mark.api_con_required
|
||||||
def test_get_sales_prognosis_data_FailApiServer(session, mock_get):
|
def test_get_sales_prognosis_data_FailApiServer(session, mock_get):
|
||||||
code = 405
|
code = 405
|
||||||
@@ -101,11 +127,6 @@ def test_get_sales_prognosis_data_FailApiServer(session, mock_get):
|
|||||||
def test_get_sales_prognosis_data_FailGetTimeout(session, mock_get):
|
def test_get_sales_prognosis_data_FailGetTimeout(session, mock_get):
|
||||||
mock_get.side_effect = requests.exceptions.Timeout("Test timeout")
|
mock_get.side_effect = requests.exceptions.Timeout("Test timeout")
|
||||||
|
|
||||||
def assert_login():
|
|
||||||
return LoginResponse(token=""), requests_.STATUS_HANDLER.SUCCESS
|
|
||||||
|
|
||||||
session.assert_login = assert_login
|
|
||||||
|
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
||||||
assert resp is not None
|
assert resp is not None
|
||||||
assert len(resp.daten) == 0
|
assert len(resp.daten) == 0
|
||||||
@@ -115,11 +136,6 @@ def test_get_sales_prognosis_data_FailGetTimeout(session, mock_get):
|
|||||||
def test_get_sales_prognosis_data_FailGetRequestException(session, mock_get):
|
def test_get_sales_prognosis_data_FailGetRequestException(session, mock_get):
|
||||||
mock_get.side_effect = requests.exceptions.RequestException("Test not timeout")
|
mock_get.side_effect = requests.exceptions.RequestException("Test not timeout")
|
||||||
|
|
||||||
def assert_login():
|
|
||||||
return LoginResponse(token=""), requests_.STATUS_HANDLER.SUCCESS
|
|
||||||
|
|
||||||
session.assert_login = assert_login
|
|
||||||
|
|
||||||
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
||||||
assert resp is not None
|
assert resp is not None
|
||||||
assert len(resp.daten) == 0
|
assert len(resp.daten) == 0
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from datetime import datetime as Datetime
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -47,6 +48,8 @@ def test_write_performance_metrics_FailStartingTime(session):
|
|||||||
|
|
||||||
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
||||||
def test_sales_prognosis_pipeline(exmpl_api_sales_prognosis_resp, session, monkeypatch):
|
def test_sales_prognosis_pipeline(exmpl_api_sales_prognosis_resp, session, monkeypatch):
|
||||||
|
date = Datetime(2023, 8, 15)
|
||||||
|
company_ids = [5661, 1027, 1024]
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"delta_barth.analysis.forecast.get_sales_prognosis_data",
|
"delta_barth.analysis.forecast.get_sales_prognosis_data",
|
||||||
@@ -56,7 +59,7 @@ def test_sales_prognosis_pipeline(exmpl_api_sales_prognosis_resp, session, monke
|
|||||||
):
|
):
|
||||||
get_mock.return_value = (exmpl_api_sales_prognosis_resp, STATUS_HANDLER.SUCCESS)
|
get_mock.return_value = (exmpl_api_sales_prognosis_resp, STATUS_HANDLER.SUCCESS)
|
||||||
sess_mock.cfg.forecast.threshold_month_data_points = 1
|
sess_mock.cfg.forecast.threshold_month_data_points = 1
|
||||||
json_export = pl.pipeline_sales_forecast(None, None)
|
json_export = pl.pipeline_sales_forecast(company_ids, date)
|
||||||
|
|
||||||
assert isinstance(json_export, str)
|
assert isinstance(json_export, str)
|
||||||
parsed_resp = json.loads(json_export)
|
parsed_resp = json.loads(json_export)
|
||||||
|
|||||||
@@ -314,11 +314,11 @@ def test_login_logout_FailApiServer(session, mock_put):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
@pytest.mark.api_con_required
|
||||||
def test_assert_login_SuccessLoggedOut(session):
|
def test_relogin_SuccessLoggedOut(session):
|
||||||
assert session.session_token is None
|
assert session.session_token is None
|
||||||
assert session._creds is not None
|
assert session._creds is not None
|
||||||
# test logged out state
|
# test logged out state
|
||||||
resp, status = session.assert_login()
|
resp, status = session.relogin()
|
||||||
assert resp is not None
|
assert resp is not None
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
assert session.session_token is not None
|
assert session.session_token is not None
|
||||||
@@ -327,74 +327,17 @@ def test_assert_login_SuccessLoggedOut(session):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
@pytest.mark.api_con_required
|
||||||
def test_assert_login_SuccessStillLoggedIn(session):
|
def test_relogin_SuccessStillLoggedIn(session):
|
||||||
assert session.session_token is None
|
assert session.session_token is None
|
||||||
assert session._creds is not None
|
assert session._creds is not None
|
||||||
resp, status = session.login()
|
resp, status = session.login()
|
||||||
resp, status = session.assert_login()
|
old_token = session.session_token
|
||||||
|
assert old_token is not None
|
||||||
|
resp, status = session.relogin()
|
||||||
assert resp is not None
|
assert resp is not None
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
assert session.session_token is not None
|
assert session.session_token is not None
|
||||||
|
assert session.session_token != old_token
|
||||||
|
|
||||||
resp, status = session.logout()
|
resp, status = session.logout()
|
||||||
assert status.code == 0
|
assert status.code == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
|
||||||
def test_assert_login_ReloginNoValidAuth(session, mock_get):
|
|
||||||
code = 401
|
|
||||||
json = {
|
|
||||||
"message": "AuthentificationError",
|
|
||||||
"code": "TestAssertLoginAfter",
|
|
||||||
"hints": "TestCase",
|
|
||||||
}
|
|
||||||
mock_get.return_value.status_code = code
|
|
||||||
mock_get.return_value.json.return_value = json
|
|
||||||
|
|
||||||
resp, status = session.login()
|
|
||||||
|
|
||||||
resp, status = session.assert_login()
|
|
||||||
assert resp is not None
|
|
||||||
assert status.code == 0
|
|
||||||
assert session.session_token is not None
|
|
||||||
resp, status = session.logout()
|
|
||||||
assert status.code == 0
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
|
||||||
def test_assert_login_ReloginWrongToken(session):
|
|
||||||
# triggers code 401
|
|
||||||
assert session.session_token is None
|
|
||||||
assert session._creds is not None
|
|
||||||
_, status = session.login()
|
|
||||||
assert status.code == 0
|
|
||||||
session._session_token = "WRONGTOKEN"
|
|
||||||
resp, status = session.assert_login()
|
|
||||||
assert resp is not None
|
|
||||||
assert status.code == 0
|
|
||||||
assert session.session_token is not None
|
|
||||||
resp, status = session.logout()
|
|
||||||
assert status.code == 0
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.api_con_required
|
|
||||||
def test_assert_login_FailApiServer(session, mock_get):
|
|
||||||
code = 500
|
|
||||||
json = {
|
|
||||||
"message": "ServerError",
|
|
||||||
"code": "TestExternalServerError",
|
|
||||||
"hints": "TestCase",
|
|
||||||
}
|
|
||||||
mock_get.return_value.status_code = code
|
|
||||||
mock_get.return_value.json.return_value = json
|
|
||||||
|
|
||||||
resp, status = session.login()
|
|
||||||
|
|
||||||
resp, status = session.assert_login()
|
|
||||||
assert resp is not None
|
|
||||||
assert not resp.token
|
|
||||||
assert status.code == 400
|
|
||||||
assert status.api_server_error is not None
|
|
||||||
assert status.api_server_error.status_code == code
|
|
||||||
assert status.api_server_error.message == json["message"]
|
|
||||||
assert status.api_server_error.code == json["code"]
|
|
||||||
assert status.api_server_error.hints == json["hints"]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user