diff --git a/halfapi/cli/cli.py b/halfapi/cli/cli.py index 819c91d..1e081a6 100644 --- a/halfapi/cli/cli.py +++ b/halfapi/cli/cli.py @@ -10,11 +10,6 @@ def cli(ctx, version): if version: import halfapi return click.echo(halfapi.version) - - #if not IS_PROJECT: - # return init() - #if ctx.invoked_subcommand is None: - # return run() if IS_PROJECT: import halfapi.cli.domain diff --git a/halfapi/cli/domain.py b/halfapi/cli/domain.py index f6f8995..41af4f5 100644 --- a/halfapi/cli/domain.py +++ b/halfapi/cli/domain.py @@ -15,48 +15,15 @@ from halfapi.db import ( logger = logging.getLogger('halfapi') +################# +# domain create # +################# +def create_domain(name): + pass -def delete_domain(domain): - d = Domain(name=domain) - if len(d) != 1: - return False - - d.delete(delete_all=True) - return True - - -@click.option('--domain', '-d', default=None, multiple=True) -@click.option('--update', default=False, is_flag=True) -@cli.command() -def domain(domain, update): - """ - Lists routes for the specified domains, or update them in the database - - Parameters: - domain (List[str]|None): The list of the domains to list/update - - The parameter has a misleading name as it is a multiple option - but this would be strange to use it several times named as "domains" - - update (boolean): If set, update the database for the selected domains - """ - - if not domain: - domain = DOMAINS - else: - for domain_name in domain: - if domain_name in DOMAINS: - continue - click.echo( - f'Domain {domain}s is not activated in the configuration') - - if update: - update_db(domain) - else: - for domain_name in domain: - list_routes(domain) - - +############### +# domain read # +############### def list_routes(domain): click.echo(f'\nDomain : {domain}') routers = APIRouter(domain=domain) @@ -69,7 +36,9 @@ def list_routes(domain): route['acls'] = acls click.echo('- [{http_verb}] {path} ({acls})'.format(**route)) - +################# +# domain update # +################# def update_db(domains): def add_domain(domain): @@ -279,3 +248,63 @@ def update_db(domains): # @TODO : Insertion exception handling print(e) continue + + +################# +# domain delete # +################# +def delete_domain(domain): + d = Domain(name=domain) + if len(d) != 1: + return False + + d.delete(delete_all=True) + return True + + +@click.option('--read',default=False, is_flag=True) +@click.option('--create',default=False, is_flag=True) +@click.option('--update',default=False, is_flag=True) +@click.option('--delete',default=False, is_flag=True) +@click.option('--domains',default=None) +@cli.command() +def domain(domains, delete, update, create, read): #, domains, read, create, update, delete): + """ + Lists routes for the specified domains, or update them in the database + + Parameters: + domain (List[str]|None): The list of the domains to list/update + + The parameter has a misleading name as it is a multiple option + but this would be strange to use it several times named as "domains" + + update (boolean): If set, update the database for the selected domains + """ + + if not domains: + if create: + return create_domain() + + domains = DOMAINS + else: + domains_ = [] + for domain_name in domains.split(','): + if domain_name in DOMAINS: + domains.append(domain_name) + continue + + click.echo( + f'Domain {domain_name}s is not activated in the configuration') + + domains = domains_ + + update = False + for domain in domains: + if update: + update_db(domain) + if delete: + delete_domain(domain) + else: + list_routes(domain) + + diff --git a/halfapi/cli/init.py b/halfapi/cli/init.py index 46e07b9..48f47b0 100644 --- a/halfapi/cli/init.py +++ b/halfapi/cli/init.py @@ -12,9 +12,8 @@ from .cli import cli logger = logging.getLogger('halfapi') -TMPL_HALFAPI_ETC = """Insert this into the HALFAPI_CONF_DIR/{project} file - -[project] +TMPL_HALFAPI_ETC = """[project] +name = {project} host = 127.0.0.1 port = 8000 secret = /path/to/secret_file @@ -22,6 +21,12 @@ production = False base_dir = {base_dir} """ +def format_halfapi_etc(project, path): + return TMPL_HALFAPI_ETC.format( + project=project, + base_dir=path + ) + TMPL_HALFAPI_CONFIG = """[project] name = {name} halfapi_version = {halfapi_version} @@ -59,8 +64,8 @@ def init(project): halfapi_version=__version__ )) - print(TMPL_HALFAPI_ETC.format( - project=project, - base_dir=os.path.abspath(project) - )) + click.echo(f'Insert this into the HALFAPI_CONF_DIR/{project} file') + click.echo(format_halfapi_etc( + project, + os.path.abspath(project))) diff --git a/halfapi/conf.py b/halfapi/conf.py index 7dd590e..d1a21af 100644 --- a/halfapi/conf.py +++ b/halfapi/conf.py @@ -7,6 +7,7 @@ from configparser import ConfigParser IS_PROJECT = os.path.isfile('.halfapi/config') if IS_PROJECT: + default_config = { 'project': { 'host': '127.0.0.1', @@ -35,17 +36,25 @@ if IS_PROJECT: CONF_DIR = environ.get('HALFAPI_CONF_DIR', '/etc/half_api') - config.read(filenames=[os.path.join( + HALFAPI_CONF_FILE=os.path.join( CONF_DIR, - PROJECT_NAME - )]) + PROJECT_NAME + ) + if not os.path.isfile(HALFAPI_CONF_FILE): + print(f'Missing {HALFAPI_CONF_FILE}, exiting') + sys.exit(1) + config.read(filenames=[HALFAPI_CONF_FILE]) HOST = config.get('project', 'host') PORT = config.getint('project', 'port') DB_NAME = f'halfapi_{PROJECT_NAME}' - with open(config.get('project', 'secret')) as secret_file: - SECRET = secret_file.read() + try: + with open(config.get('project', 'secret')) as secret_file: + SECRET = secret_file.read() + except FileNotFoundError: + print('There is no file like {}'.format(config.get('project', 'secret'))) + sys.exit(1) PRODUCTION = config.getboolean('project', 'production') BASE_DIR = config.get('project', 'base_dir') diff --git a/tests/conftest.py b/tests/conftest.py index 3cb2e0c..e755c9d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,57 @@ -# content of conftest.py - +#!/usr/bin/env python3 +import re +import os +import subprocess +import importlib +import tempfile +from unittest.mock import patch from typing import Dict, Tuple import pytest +from uuid import uuid1 +from click.testing import CliRunner +from halfapi import __version__ +from halfapi.cli import cli +from halfapi.cli.init import format_halfapi_etc +Cli = cli.cli + +PROJNAME = os.environ.get('PROJ','tmp_api') + +@pytest.fixture +def runner(): + return CliRunner() + + +@pytest.fixture +def dropdb(): + p = subprocess.Popen(['dropdb', f'halfapi_{PROJNAME}']) + p.wait() + yield + + p = subprocess.Popen(['dropdb', f'halfapi_{PROJNAME}']) + p.wait() + +@pytest.fixture +def createdb(): + p = subprocess.Popen(['createdb', f'halfapi_{PROJNAME}']) + p.wait() + return + +def confdir(dirname): + d = os.environ.get(dirname) + if not d: + os.environ[dirname] = tempfile.mkdtemp(prefix='halfapi_') + return os.environ.get(dirname) + if not os.path.isdir(d): + os.mkdir(d) + return d + +@pytest.fixture +def halform_conf_dir(): + return confdir('HALFORM_CONF_DIR') + +@pytest.fixture +def halfapi_conf_dir(): + return confdir('HALFAPI_CONF_DIR') # store history of failures per test class name and per index in parametrize (if # parametrize used) @@ -51,3 +101,28 @@ def pytest_runtest_setup(item): # test name if test_name is not None: pytest.xfail("previous test failed ({})".format(test_name)) + +@pytest.fixture +def project_runner(runner, dropdb, createdb, halform_conf_dir, halfapi_conf_dir): + env = { + 'HALFORM_CONF_DIR': halform_conf_dir, + 'HALFAPI_CONF_DIR': halfapi_conf_dir + } + with runner.isolated_filesystem(): + res = runner.invoke(Cli, ['init', PROJNAME], + env=env, + catch_exceptions=True) + assert res.exit_code == 0 + + os.chdir(PROJNAME) + secret = tempfile.mkstemp() + SECRET_PATH = secret[1] + with open(SECRET_PATH, 'w') as f: + f.write(str(uuid1())) + + with open(os.path.join(halfapi_conf_dir, PROJNAME), 'w') as f: + PROJ_CONFIG = re.sub('secret = .*', f'secret = {SECRET_PATH}', + format_halfapi_etc(PROJNAME, os.getcwd())) + f.write(PROJ_CONFIG) + + yield lambda args: runner.invoke(Cli, args, env=env) diff --git a/tests/test_cli.py b/tests/test_cli.py index 21fc9d6..f646eab 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,8 @@ import os import subprocess import importlib +import tempfile +from unittest.mock import patch import pytest from click.testing import CliRunner @@ -13,26 +15,6 @@ Cli = cli.cli PROJNAME = os.environ.get('PROJ','tmp_api') -@pytest.fixture -def runner(): - return CliRunner() - - -@pytest.fixture -def dropdb(): - p = subprocess.Popen(['dropdb', f'halfapi_{PROJNAME}']) - p.wait() - yield - - p = subprocess.Popen(['dropdb', f'halfapi_{PROJNAME}']) - p.wait() - -@pytest.fixture -def createdb(): - p = subprocess.Popen(['createdb', f'halfapi_{PROJNAME}']) - p.wait() - return - @pytest.mark.incremental class TestCli(): @@ -79,12 +61,12 @@ class TestCli(): r = runner.invoke(Cli, ['init', testproject]) assert r.exit_code == 1 - def test_init_project(self, runner, dropdb, createdb): + def test_init_project(self, runner, dropdb, createdb, halform_conf_dir, halfapi_conf_dir): cp = ConfigParser() with runner.isolated_filesystem(): env = { - 'HALFORM_CONF_DIR': os.environ.get('HALFORM_CONF_DIR', os.getcwd()), - 'HALFAPI_CONF_DIR': os.environ.get('HALFAPI_CONF_DIR', os.getcwd()), + 'HALFORM_CONF_DIR': halform_conf_dir, + 'HALFAPI_CONF_DIR': halfapi_conf_dir } res = runner.invoke(Cli, ['init', PROJNAME], env=env) @@ -105,96 +87,10 @@ class TestCli(): assert os.path.isfile(os.path.join(PROJNAME, '.halfapi', 'domains')) cp.read(os.path.join(PROJNAME, '.halfapi', 'domains')) assert cp.has_section('domains') - except AssertionError: + except AssertionError as e: subprocess.run(['tree', '-a', os.getcwd()]) + raise e assert res.exit_code == 0 assert res.exception is None - def test_run_commands(self, runner, dropdb, createdb): - def reloadcli(): - importlib.reload(cli) - return cli.cli - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - assert res.exit_code == 0 - os.chdir(PROJNAME) - Cli2 = reloadcli() - res = runner.invoke(Cli2, ['run', '--help']) - assert res.exception is None - assert res.exit_code == 0 - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['run', 'foobar']) - assert res.exit_code == 2 - - - def test_domain_commands(self, runner, dropdb, createdb): - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['domain', 'foobar']) - assert res.exit_code == 2 - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['domain', '--help']) - assert r.exit_code == 0 - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['domain', 'create', '--help']) - assert r.exit_code == 0 - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['domain', 'read', '--help']) - assert r.exit_code == 0 - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['domain', 'update', '--help']) - assert r.exit_code == 0 - - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - os.chdir(PROJNAME) - res = runner.invoke(Cli, ['domain', 'delete', '--help']) - assert r.exit_code == 0 - - def test_domain_create(self, runner, dropdb): - with runner.isolated_filesystem(): - res = runner.invoke(Cli, ['init', PROJNAME]) - assert res.exit_code == 0 - - os.chdir(PROJNAME) - - DOMNAME='tmp_domain' - res = runner.invoke(Cli, ['domain', 'create', DOMNAME]) - srcdir = os.path.join('domains', 'src', DOMNAME) - assert os.path.isdir(srcdir) - moddir = os.path.join(srcdir, DOMNAME) - assert os.path.isdir(moddir) - setup = os.path.join(srcdir, 'setup.py') - assert os.path.isfile(setup) - initfile = os.path.join(moddir, '__init__.py') - assert os.path.isfile(initfile) - aclfile = os.path.join(moddir, 'acl.py') - assert os.path.isfile(aclfile) - aclsdir = os.path.join(moddir, 'acls') - assert os.path.isdir(aclsdir) - routersdir = os.path.join(moddir, 'routers') - assert os.path.isdir(routersdir) - - try: - dom_mod = importlib.import_module(DOMNAME, srcdir) - assert hasattr(dom_mod, 'ROUTERS') - except ImportError: - assert False