317 lines
8.4 KiB
Python
317 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
import logging
|
|
import functools
|
|
import re
|
|
import os
|
|
import subprocess
|
|
import importlib
|
|
import tempfile
|
|
from typing import Dict, Tuple
|
|
from uuid import uuid1, uuid4, UUID
|
|
from click.testing import CliRunner
|
|
import jwt
|
|
import sys
|
|
from unittest.mock import patch
|
|
import pytest
|
|
from starlette.applications import Starlette
|
|
from starlette.responses import PlainTextResponse
|
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
from starlette.testclient import TestClient
|
|
from halfapi import __version__
|
|
from halfapi.halfapi import HalfAPI
|
|
from halfapi.cli.cli import cli
|
|
from halfapi.cli.init import init, format_halfapi_etc
|
|
from halfapi.cli.domain import domain, create_domain
|
|
from halfapi.lib.responses import ORJSONResponse
|
|
from halfapi.lib.jwt_middleware import JWTAuthenticationBackend
|
|
|
|
logger = logging.getLogger()
|
|
|
|
PROJNAME = os.environ.get('PROJ','tmp_api')
|
|
|
|
SECRET = 'dummysecret'
|
|
|
|
from halfapi.lib.jwt_middleware import (
|
|
JWTUser, JWTAuthenticationBackend,
|
|
JWTWebSocketAuthenticationBackend)
|
|
|
|
@pytest.fixture
|
|
def dummy_domain():
|
|
yield {
|
|
'name': 'dummy_domain',
|
|
'router': 'dummy_domain.routers',
|
|
'config': {
|
|
'test': True
|
|
}
|
|
}
|
|
|
|
@pytest.fixture
|
|
def token_builder():
|
|
yield jwt.encode({
|
|
'name':'xxx',
|
|
'user_id': str(uuid4())},
|
|
key=SECRET
|
|
)
|
|
|
|
@pytest.fixture
|
|
def token_debug_false_builder():
|
|
yield jwt.encode({
|
|
'name':'xxx',
|
|
'user_id': str(uuid4()),
|
|
'debug': False},
|
|
key=SECRET
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def token_debug_true_builder():
|
|
yield jwt.encode({
|
|
'name':'xxx',
|
|
'user_id': str(uuid4()),
|
|
'debug': True},
|
|
key=SECRET
|
|
)
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def cli_runner():
|
|
"""Yield a click.testing.CliRunner to invoke the CLI."""
|
|
class_ = CliRunner
|
|
|
|
def invoke_wrapper(f):
|
|
"""Augment CliRunner.invoke to emit its output to stdout.
|
|
|
|
This enables pytest to show the output in its logs on test
|
|
failures.
|
|
|
|
"""
|
|
@functools.wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
echo = kwargs.pop('echo', False)
|
|
result = f(*args, **kwargs)
|
|
|
|
if echo is True:
|
|
sys.stdout.write(result.output)
|
|
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
class_.invoke = invoke_wrapper(class_.invoke)
|
|
cli_runner_ = class_()
|
|
|
|
yield cli_runner_
|
|
|
|
@pytest.fixture
|
|
def halfapicli(cli_runner):
|
|
def caller(*args):
|
|
return cli_runner.invoke(cli, ' '.join(args))
|
|
|
|
yield caller
|
|
|
|
|
|
# store history of failures per test class name and per index in parametrize (if
|
|
# parametrize used)
|
|
_test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {}
|
|
|
|
|
|
def pytest_runtest_makereport(item, call):
|
|
if "incremental" in item.keywords:
|
|
# incremental marker is used
|
|
if call.excinfo is not None:
|
|
# the test has failed
|
|
# retrieve the class name of the test
|
|
cls_name = str(item.cls)
|
|
# retrieve the index of the test (if parametrize is used in
|
|
# combination with incremental)
|
|
parametrize_index = (
|
|
tuple(item.callspec.indices.values())
|
|
if hasattr(item, "callspec")
|
|
else ()
|
|
)
|
|
# retrieve the name of the test function
|
|
test_name = item.originalname or item.name
|
|
# store in _test_failed_incremental the original name of the failed
|
|
# test
|
|
_test_failed_incremental.setdefault(cls_name, {}).setdefault(
|
|
parametrize_index, test_name
|
|
)
|
|
|
|
|
|
def pytest_runtest_setup(item):
|
|
if "incremental" in item.keywords:
|
|
# retrieve the class name of the test
|
|
cls_name = str(item.cls)
|
|
# check if a previous test has failed for this class
|
|
if cls_name in _test_failed_incremental:
|
|
# retrieve the index of the test (if parametrize is used in
|
|
# combination with incremental)
|
|
parametrize_index = (
|
|
tuple(item.callspec.indices.values())
|
|
if hasattr(item, "callspec")
|
|
else ()
|
|
)
|
|
# retrieve the name of the first test function to fail for this
|
|
# class name and index
|
|
test_name = _test_failed_incremental[cls_name].get(parametrize_index, None)
|
|
# if name found, test has failed for the combination of class name &
|
|
# test name
|
|
if test_name is not None:
|
|
pytest.xfail("previous test failed ({})".format(test_name))
|
|
|
|
@pytest.fixture
|
|
def project_runner(runner, halfapicli, tree):
|
|
with runner.isolated_filesystem():
|
|
res = halfapicli('init', PROJNAME)
|
|
|
|
os.chdir(PROJNAME)
|
|
|
|
fs_path = os.getcwd()
|
|
sys.path.insert(0, fs_path)
|
|
|
|
secret = tempfile.mkstemp()
|
|
SECRET_PATH = secret[1]
|
|
with open(SECRET_PATH, 'w') as f:
|
|
f.write(str(uuid1()))
|
|
|
|
with open(os.path.join('.halfapi', PROJNAME), 'w') as halfapi_etc:
|
|
PROJ_CONFIG = re.sub('secret = .*', f'secret = {SECRET_PATH}',
|
|
format_halfapi_etc(PROJNAME, os.getcwd()))
|
|
halfapi_etc.write(PROJ_CONFIG)
|
|
|
|
|
|
###
|
|
# add dummy domain
|
|
###
|
|
create_domain('test_domain', 'test_domain.routers')
|
|
###
|
|
|
|
yield halfapicli
|
|
|
|
while fs_path in sys.path:
|
|
sys.path.remove(fs_path)
|
|
|
|
@pytest.fixture
|
|
def dummy_app():
|
|
app = Starlette()
|
|
app.add_route('/',
|
|
lambda request, *args, **kwargs: PlainTextResponse('Hello test!'))
|
|
app.add_middleware(
|
|
AuthenticationMiddleware,
|
|
backend=JWTAuthenticationBackend(secret_key='dummysecret')
|
|
)
|
|
return app
|
|
|
|
@pytest.fixture
|
|
def dummy_debug_app():
|
|
app = Starlette(debug=True)
|
|
app.add_route('/',
|
|
lambda request, *args, **kwargs: PlainTextResponse('Hello test!'))
|
|
app.add_middleware(
|
|
AuthenticationMiddleware,
|
|
backend=JWTAuthenticationBackend(secret_key='dummysecret')
|
|
)
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def test_client(dummy_app):
|
|
return TestClient(dummy_app)
|
|
|
|
@pytest.fixture
|
|
def create_route():
|
|
def wrapped(domain_path, method, path):
|
|
stack = [domain_path, *path.split('/')[1:]]
|
|
for i in range(len(stack)):
|
|
if len(stack[i]) == 0:
|
|
continue
|
|
|
|
path = os.path.join(*stack[0:i+1])
|
|
if os.path.isdir(os.path.join(path)):
|
|
continue
|
|
os.mkdir(path)
|
|
init_path = os.path.join(*stack, '__init__.py')
|
|
with open(init_path, 'a+') as f:
|
|
f.write(f'\ndef {method}():\n raise NotImplementedError')
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
def dummy_project():
|
|
halfapi_config = tempfile.mktemp()
|
|
halfapi_secret = tempfile.mktemp()
|
|
domain = 'dummy_domain'
|
|
|
|
with open(halfapi_config, 'w') as f:
|
|
f.writelines([
|
|
'[project]\n',
|
|
'name = lirmm_api\n',
|
|
'halfapi_version = 0.5.0\n',
|
|
f'secret = {halfapi_secret}\n',
|
|
'port = 3050\n',
|
|
'loglevel = debug\n',
|
|
'[domain.dummy_domain]\n',
|
|
f'name = {domain}\n',
|
|
'router = dummy_domain.routers\n',
|
|
f'[domain.dummy_domain.config]\n',
|
|
'test = True'
|
|
])
|
|
|
|
with open(halfapi_secret, 'w') as f:
|
|
f.write('turlututu')
|
|
|
|
return (halfapi_config, 'dummy_domain', 'dummy_domain.routers')
|
|
|
|
@pytest.fixture
|
|
def application_debug(project_runner):
|
|
halfAPI = HalfAPI({
|
|
'secret':'turlututu',
|
|
'production':False,
|
|
'domain': {
|
|
'domain': {
|
|
'name': 'test_domain',
|
|
'router': 'test_domain.routers'
|
|
}
|
|
},
|
|
'config':{
|
|
'domain_config': {'test_domain': {'test': True}}
|
|
}
|
|
})
|
|
|
|
assert isinstance(halfAPI, HalfAPI)
|
|
yield halfAPI.application
|
|
|
|
|
|
@pytest.fixture
|
|
def application_domain(dummy_domain):
|
|
return HalfAPI({
|
|
'secret':'turlututu',
|
|
'production':True,
|
|
'domain': {
|
|
'domain': {
|
|
**dummy_domain,
|
|
'config': {
|
|
'test': True
|
|
}
|
|
}
|
|
}
|
|
}).application
|
|
|
|
|
|
@pytest.fixture
|
|
def tree():
|
|
def wrapped(path):
|
|
list_dirs = os.walk(path)
|
|
for root, dirs, files in list_dirs:
|
|
for d in dirs:
|
|
print(os.path.join(root, d))
|
|
for f in files:
|
|
print(os.path.join(root, f))
|
|
return wrapped
|