mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-05-07 22:40:49 +08:00
* start the pr * codespell * lint * lint * lint * more lint * allow parameterized dimensions to be set by 'dimension_values' pairs * missed file in commit * grammar police * more lint * frequency description * fix nasdaq test * new lint * more touchup * readme * readme * fix integration tests * fix more integration tests * list_indicators_by_dataflow is redundant * unused import * remove integration test for removed utils endpoint * update dependencies * add grouping mechanism for port info widgets. * fix nan string representation propagating in table_presentation * more presentation table updates * update lock * cli lock file
375 lines
14 KiB
Python
375 lines
14 KiB
Python
"""Test if there are any missing providers for python interface integration tests."""
|
|
|
|
import importlib.util
|
|
import inspect
|
|
import os
|
|
from collections.abc import Callable
|
|
from typing import (
|
|
Any,
|
|
Literal,
|
|
get_type_hints,
|
|
)
|
|
|
|
from openbb_core.app.provider_interface import ProviderInterface
|
|
from openbb_core.app.router import CommandMap
|
|
|
|
from .integration_tests_generator import (
|
|
find_extensions,
|
|
get_test_params_data_processing,
|
|
)
|
|
|
|
|
|
def get_integration_tests(
|
|
test_type: Literal["api", "python"], filter_charting_ext: bool | None = True
|
|
) -> list[Any]:
|
|
"""Get integration tests for the OpenBB Platform."""
|
|
integration_tests: list[Any] = []
|
|
|
|
if test_type == "python":
|
|
file_end = "_python.py"
|
|
elif test_type == "api":
|
|
file_end = "_api.py"
|
|
else:
|
|
raise ValueError(f"test_type '{test_type}' not valid")
|
|
|
|
for extension in find_extensions(filter_charting_ext):
|
|
integration_folder = os.path.join(extension, "integration")
|
|
if not os.path.exists(integration_folder):
|
|
continue
|
|
for file in os.listdir(integration_folder):
|
|
if file.endswith(file_end):
|
|
file_path = os.path.join(integration_folder, file)
|
|
module_name = file[:-3] # Remove .py from file name
|
|
|
|
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
if spec:
|
|
module = importlib.util.module_from_spec(spec)
|
|
if spec.loader:
|
|
spec.loader.exec_module(module)
|
|
integration_tests.append(module)
|
|
|
|
return integration_tests
|
|
|
|
|
|
def get_module_functions(module_list: list[Any]) -> dict[str, Any]:
|
|
"""Get all functions from a list of modules."""
|
|
functions = {}
|
|
for module in module_list:
|
|
for name, obj in inspect.getmembers(module):
|
|
if inspect.isfunction(obj):
|
|
functions[name] = obj
|
|
return functions
|
|
|
|
|
|
def check_missing_providers(
|
|
command_params: dict[str, dict[str, dict]] | list[tuple[dict[str, str], str]],
|
|
function_params: list[dict],
|
|
function,
|
|
processing: bool = False,
|
|
) -> list[str]:
|
|
"""Check if there are any missing providers for a command."""
|
|
if processing or not isinstance(command_params, dict):
|
|
return []
|
|
|
|
missing_providers: list[str] = []
|
|
providers = list(command_params.keys())
|
|
providers.remove("openbb")
|
|
if not providers:
|
|
return []
|
|
|
|
for test_params in function_params:
|
|
provider = test_params.get("provider", None)
|
|
if provider:
|
|
try: # noqa
|
|
providers.remove(provider)
|
|
except ValueError:
|
|
pass
|
|
|
|
if providers:
|
|
# if there is only one provider left and the length of the
|
|
# test_params is 1, we can ignore because it is picked up by default
|
|
if len(providers) == 1 and len(function_params) == 1:
|
|
pass
|
|
else:
|
|
missing_providers.append(
|
|
f"Missing providers for {function}: {providers} --? {function_params}"
|
|
)
|
|
|
|
return missing_providers
|
|
|
|
|
|
def check_wrong_params(
|
|
command_params: dict[str, dict[str, dict]] | list[tuple[dict[str, str], str]],
|
|
function_params: list[dict],
|
|
function,
|
|
processing: bool = False,
|
|
) -> list[str]:
|
|
"""Check if there are any wrong params passed to a command."""
|
|
wrong_params = []
|
|
if not processing:
|
|
for i, test_params in enumerate(function_params):
|
|
if "provider" in test_params and i != 0:
|
|
provider = test_params["provider"]
|
|
if provider in command_params:
|
|
for param in test_params:
|
|
if (
|
|
param
|
|
not in command_params[provider]["QueryParams"]["fields"]
|
|
and param not in command_params["openbb"]["QueryParams"]["fields"] # type: ignore
|
|
and param != "provider"
|
|
):
|
|
wrong_params.append(
|
|
f"Wrong param {param} for provider {provider} in function {function}"
|
|
)
|
|
elif isinstance(command_params, dict):
|
|
providers = list(command_params.keys())
|
|
providers.remove("openbb")
|
|
for param in test_params:
|
|
is_wrong_param = True
|
|
for provider in providers:
|
|
if (
|
|
param in command_params[provider]["QueryParams"]["fields"]
|
|
or param
|
|
in command_params["openbb"]["QueryParams"]["fields"]
|
|
or param == "provider"
|
|
):
|
|
is_wrong_param = False
|
|
break
|
|
|
|
if is_wrong_param:
|
|
wrong_params.append(
|
|
f"Wrong param {param} in function {function}"
|
|
)
|
|
|
|
else:
|
|
for test_params in function_params:
|
|
if isinstance(command_params, list):
|
|
try:
|
|
iter_commands_params = command_params[0][0]
|
|
except KeyError:
|
|
iter_commands_params = command_params[0] # type: ignore
|
|
|
|
if isinstance(test_params, dict):
|
|
param_keys = test_params.keys()
|
|
elif isinstance(test_params, tuple) and all(
|
|
isinstance(item, dict) for item in test_params
|
|
):
|
|
param_keys = [key for item in test_params for key in item]
|
|
else:
|
|
continue # Skip this iteration if test_params is neither a dict nor a tuple of dicts
|
|
|
|
for key in param_keys:
|
|
if key not in iter_commands_params and key != "return":
|
|
wrong_params.append(f"Wrong param {key} in function {function}")
|
|
return wrong_params
|
|
|
|
|
|
def check_missing_params(
|
|
command_params: dict[str, dict[str, dict]] | list[tuple[dict[str, str], str]],
|
|
function_params: list[dict],
|
|
function,
|
|
processing: bool = False,
|
|
) -> list[str]:
|
|
"""Check if there are any missing params for a command."""
|
|
missing_params = []
|
|
if not processing:
|
|
for i, test_params in enumerate(function_params):
|
|
if "provider" in test_params and i != 0:
|
|
provider = test_params["provider"]
|
|
if provider in command_params:
|
|
for expected_param in command_params[provider]["QueryParams"][
|
|
"fields"
|
|
]:
|
|
if expected_param not in test_params:
|
|
missing_params.append(
|
|
f"Missing param {expected_param} for provider {provider} in function {function}"
|
|
)
|
|
elif isinstance(command_params, dict):
|
|
for expected_param in command_params["openbb"]["QueryParams"]["fields"]:
|
|
if expected_param not in test_params:
|
|
missing_params.append(
|
|
f"Missing standard param {expected_param} in function {function}"
|
|
)
|
|
else:
|
|
for test_params in function_params:
|
|
if isinstance(command_params, list):
|
|
try:
|
|
iter_commands_params = command_params[0][0]
|
|
except KeyError:
|
|
iter_commands_params = command_params[0] # type: ignore
|
|
|
|
for expected_param in iter_commands_params:
|
|
try:
|
|
used_params = test_params[0].keys()
|
|
except KeyError:
|
|
used_params = test_params.keys()
|
|
if expected_param not in used_params and expected_param not in (
|
|
"return",
|
|
"chart",
|
|
):
|
|
missing_params.append(
|
|
f"Missing param {expected_param} in function {function}"
|
|
)
|
|
return missing_params
|
|
|
|
|
|
def check_integration_tests(
|
|
functions: dict[str, Any],
|
|
check_function: Callable[
|
|
[
|
|
dict[str, dict[str, dict]] | list[tuple[dict[str, str], str]],
|
|
list[dict],
|
|
str,
|
|
bool,
|
|
],
|
|
list[str],
|
|
],
|
|
) -> list[str]:
|
|
"""Check if there are any missing items for integration tests."""
|
|
pi = ProviderInterface()
|
|
provider_interface_map = pi.map
|
|
cm = CommandMap(coverage_sep=".")
|
|
|
|
function_params: list[dict] = []
|
|
all_missing_items: list[str] = []
|
|
used_functions: list[str] = []
|
|
|
|
for command, model in cm.commands_model.items():
|
|
for function in functions:
|
|
if command[1:].replace(".", "_") == function.replace("test_", ""):
|
|
command_params: dict[str, dict[str, dict]] = provider_interface_map[
|
|
model
|
|
]
|
|
try:
|
|
function_params = (
|
|
functions[function].pytestmark[1].args[1]
|
|
if len(functions[function].pytestmark[1].args) > 1
|
|
else []
|
|
)
|
|
except IndexError:
|
|
# Another decorator is below the parametrize decorator
|
|
function_params = functions[function].pytestmark[2].args[1]
|
|
missing_items = check_function(
|
|
command_params, function_params, function, False
|
|
)
|
|
all_missing_items.extend(missing_items)
|
|
used_functions.append(function)
|
|
|
|
# the processing commands are the ones that are left
|
|
processing_functions = [
|
|
function for function in functions if function not in used_functions
|
|
]
|
|
|
|
for route, _ in cm.map.items():
|
|
for function in processing_functions:
|
|
if route.replace("/", "_")[1:] == function.replace("test_", ""):
|
|
hints = get_type_hints(cm.map[route])
|
|
processing_command_params = [
|
|
{k: "" for k in get_test_params_data_processing(hints)}
|
|
]
|
|
if (
|
|
not processing_command_params
|
|
or len(functions[function].pytestmark) < 2
|
|
):
|
|
# If there are no params, we can skip this function
|
|
continue
|
|
try:
|
|
function_params = functions[function].pytestmark[1].args[1]
|
|
except IndexError:
|
|
# Another decorator is below the parametrize decorator
|
|
function_params = functions[function].pytestmark[2].args[1]
|
|
|
|
missing_items = check_function(
|
|
processing_command_params,
|
|
function_params,
|
|
function,
|
|
True, # type: ignore
|
|
)
|
|
|
|
# if "chart" is in missing_items, remove it
|
|
if "chart" in missing_items:
|
|
missing_items.remove("chart")
|
|
|
|
all_missing_items.extend(missing_items)
|
|
|
|
return all_missing_items
|
|
|
|
|
|
def check_missing_integration_tests(test_type: Literal["api", "python"]) -> list[str]:
|
|
"""Check if all endpoints have integration tests."""
|
|
cm = CommandMap(coverage_sep=".")
|
|
routes = [route[1:].replace("/", "_") for route in cm.map]
|
|
missing_integration_tests: list[str] = []
|
|
|
|
if test_type == "api":
|
|
functions = get_module_functions(get_integration_tests(test_type="api"))
|
|
else:
|
|
functions = get_module_functions(get_integration_tests(test_type="python"))
|
|
|
|
tested_functions = [
|
|
function.replace("test_", "", 1)
|
|
for function in functions
|
|
if function.startswith("test_")
|
|
]
|
|
|
|
for route in routes:
|
|
if route not in tested_functions:
|
|
# TODO: See how to handle edge cases that are excluded from the schema
|
|
# on purpose. This is currently on the econometrics router.
|
|
if (
|
|
test_type == "api"
|
|
and "econometrics" in route
|
|
or route.endswith(".json")
|
|
):
|
|
continue
|
|
missing_integration_tests.append(
|
|
f"Missing {test_type} integration test for route {route}"
|
|
)
|
|
|
|
return missing_integration_tests
|
|
|
|
|
|
def check_outdated_integration_tests(test_type: Literal["api", "python"]) -> list[str]:
|
|
"""Check if there are any outdated integration tests."""
|
|
cm = CommandMap(coverage_sep=".")
|
|
routes = [route[1:].replace("/", "_") for route in cm.map]
|
|
outdated_integration_tests: list[str] = []
|
|
|
|
if test_type == "api":
|
|
functions = get_module_functions(get_integration_tests(test_type="api"))
|
|
else:
|
|
functions = get_module_functions(get_integration_tests(test_type="python"))
|
|
|
|
for function, f_callable in functions.items():
|
|
if function.startswith("test_"):
|
|
route = function.replace("test_", "", 1)
|
|
try:
|
|
if f_callable.pytestmark[1].name != "skip":
|
|
args = f_callable.pytestmark[1].args[1]
|
|
else:
|
|
continue
|
|
except IndexError:
|
|
continue
|
|
if route not in routes and len(args) > 0:
|
|
# If it doesn't have any args it is because it is not installed.
|
|
outdated_integration_tests.append(
|
|
f"Outdated {test_type} integration test for route {route}"
|
|
)
|
|
|
|
return outdated_integration_tests
|
|
|
|
|
|
def check_missing_integration_test_providers(functions: dict[str, Any]) -> list[str]:
|
|
"""Check if there are any missing providers for integration tests."""
|
|
return check_integration_tests(functions, check_missing_providers)
|
|
|
|
|
|
def check_missing_integration_test_params(functions: dict[str, Any]) -> list[str]:
|
|
"""Check if there are any missing params for integration tests."""
|
|
return check_integration_tests(functions, check_missing_params)
|
|
|
|
|
|
def check_wrong_integration_test_params(functions: dict[str, Any]) -> list[str]:
|
|
"""Check if there are any wrong params for integration tests."""
|
|
return check_integration_tests(functions, check_wrong_params)
|