def test_add_api_multiple_servers(
    mocker: ptm.MockFixture,
    caplog: _logging.LogCaptureFixture,
) -> None:
    loaded_spec = mocker.stub()
    loaded_spec.servers = [
        model.OASServer(url='/v1', variables={}),
        model.OASServer(url='/v2', variables={}),
        model.OASServer(url='/v3', variables={}),
    ]

    mocker.patch('axion.plugins._aiohttp._apply_specification')

    the_app = app.AioHttpPlugin(configuration=mocker.ANY)
    the_app.add_api(spec=loaded_spec)

    assert len(the_app.api_base_paths) == 1

    assert '/v1' in the_app.api_base_paths
    assert '/v2' not in the_app.api_base_paths
    assert '/v3' not in the_app.api_base_paths

    msg = ('There are 3 servers, axion will assume first one. '
           'This behavior might change in the future, once axion knows '
           'how to deal with multiple servers')
    assert next(
        filter(
            lambda r: r.levelname.lower() == 'warning' and r.msg == msg,
            caplog.records,
        ),
        None,
    ) is not None
def test_app_add_api_different_base_path(
    mocker: ptm.MockFixture,
    tmp_path: Path,
) -> None:
    spec_one = mocker.stub()
    spec_one.servers = [model.OASServer(url='/v1', variables={})]

    spec_two = mocker.stub()
    spec_two.servers = [model.OASServer(url='/v2', variables={})]

    spec_admin = mocker.stub()
    spec_admin.servers = [model.OASServer(url='/admin', variables={})]

    apply_spec = mocker.patch('axion.plugins._aiohttp._apply_specification')

    the_app = app.AioHttpPlugin(configuration=mocker.ANY)

    the_app.add_api(spec_one)
    the_app.add_api(spec_two)
    the_app.add_api(spec_admin)

    router_resources = list(the_app.root_app.router.resources())
    assert len(router_resources) == 3
    assert len(the_app.api_base_paths) == 3

    for prefix, the_spec in (
        ('/v1', spec_one),
        ('/v2', spec_two),
        ('/admin', spec_admin),
    ):
        sub_app_a = the_app.api_base_paths.get(prefix, None)
        sub_app_b = next(
            (r for r in router_resources if r.canonical == prefix), None)

        assert sub_app_a is not None
        assert isinstance(
            sub_app_a,
            web.Application,
        )

        assert sub_app_b is not None
        assert isinstance(
            sub_app_b,
            web_urldispatcher.PrefixedSubAppResource,
        )

        assert sub_app_a == sub_app_b._app
        apply_spec.assert_any_call(
            for_app=sub_app_b._app,
            spec=the_spec,
        )
        apply_spec.assert_any_call(
            for_app=sub_app_a,
            spec=the_spec,
        )
def test_app_add_api_duplicated_base_path(
    server_url: str,
    mocker: ptm.MockFixture,
) -> None:
    spec_one = mocker.stub()
    spec_one.servers = [model.OASServer(url=server_url, variables={})]

    spec_two = mocker.stub()
    spec_two.servers = [model.OASServer(url=server_url, variables={})]

    apply_spec = mocker.patch('axion.plugins._aiohttp._apply_specification')

    the_app = app.AioHttpPlugin(configuration=mocker.ANY)
    the_app.add_api(spec_one)
    with pytest.raises(
            app.DuplicateBasePath,
            match=(f'You tried to add API with base_path={server_url}, '
                   f'but it is already added. '
                   f'If you want to add more than one API, you will need to '
                   f'specify unique base paths for each API. '
                   f'You can do this either via OAS\'s "servers" property or '
                   f'base_path argument of this function.'),
    ):
        the_app.add_api(spec_two)

    assert len(the_app.api_base_paths) == 1

    assert mock.call(
        for_app=the_app.api_base_paths[server_url],
        spec=spec_one,
    ) in apply_spec.call_args_list
    assert mock.call(
        for_app=the_app.api_base_paths[server_url],
        spec=spec_two,
    ) not in apply_spec.call_args_list

    if server_url == '/':
        apply_spec.assert_called_once_with(
            for_app=the_app.root_app,
            spec=spec_one,
        )
        assert not the_app.root_app._subapps
    else:
        assert the_app.root_app._subapps
        assert 1 == len(the_app.root_app._subapps)
        apply_spec.assert_called_once_with(
            for_app=the_app.root_app._subapps[-1],
            spec=spec_one,
        )
def test_app_add_api_overlapping_base_paths(
    server_url: str,
    overlapping_server_url: str,
    mocker: ptm.MockFixture,
) -> None:
    spec_one = mocker.stub()
    spec_one.servers = [model.OASServer(url=server_url, variables={})]

    spec_two = mocker.stub()
    spec_two.servers = [
        model.OASServer(url=overlapping_server_url, variables={})
    ]

    apply_spec = mocker.patch('axion.plugins._aiohttp._apply_specification')

    the_app = app.AioHttpPlugin(configuration=mocker.ANY)

    the_app.add_api(spec_one)
    with pytest.raises(
            app.OverlappingBasePath,
            match=
        (f'You tried to add API with base_path={overlapping_server_url}, '
         f'but it is overlapping one of the APIs that has been already added. '
         f'You need to make sure that base paths for all APIs do '
         f'not overlap each other.'),
    ):
        the_app.add_api(spec_two)

    assert len(the_app.api_base_paths) == 1
    apply_spec.assert_called_once_with(
        for_app=the_app.api_base_paths[server_url],
        spec=spec_one,
    )

    if server_url == '/':
        apply_spec.assert_called_once_with(
            for_app=the_app.root_app,
            spec=spec_one,
        )
        assert not the_app.root_app._subapps
    else:
        assert the_app.root_app._subapps
        assert 1 == len(the_app.root_app._subapps)
        apply_spec.assert_called_once_with(
            for_app=the_app.root_app._subapps[-1],
            spec=spec_one,
        )
def test_add_api_single_server(
    server_base_path: str,
    add_api_base_path: t.Optional[str],
    mocker: ptm.MockFixture,
    tmp_path: Path,
) -> None:
    loaded_spec = mocker.stub()
    loaded_spec.servers = [
        model.OASServer(
            url=server_base_path,
            variables={},
        )
    ]

    apply_spec = mocker.patch('axion.plugins._aiohttp._apply_specification')
    get_base_path = mocker.patch(
        'axion.plugins._aiohttp._get_base_path',
        return_value=server_base_path,
    )

    the_app = app.AioHttpPlugin(configuration=mocker.ANY)
    the_app.add_api(loaded_spec, add_api_base_path)

    assert len(the_app.api_base_paths) == 1

    if add_api_base_path is not None:

        assert add_api_base_path in the_app.api_base_paths
        assert server_base_path not in the_app.api_base_paths

        get_base_path.assert_not_called()
        apply_spec.assert_called_once_with(
            for_app=the_app.api_base_paths[add_api_base_path],
            spec=loaded_spec,
        )
    else:

        assert server_base_path in the_app.api_base_paths
        assert add_api_base_path not in the_app.api_base_paths

        get_base_path.assert_called_once_with(loaded_spec.servers)
        apply_spec.assert_called_once_with(
            for_app=the_app.api_base_paths[server_base_path],
            spec=loaded_spec,
        )
def test_apply_specification_no_subapp(
    spec_path: Path,
    mocker: ptm.MockFixture,
) -> None:
    the_spec = loader.load_spec(spec_path)
    the_app = app.AioHttpPlugin(configuration=mocker.ANY)

    add_route_spy = mocker.spy(the_app.root_app.router, 'add_route')
    resolve_handler = mocker.patch('axion.handler.resolve')

    the_app.add_api(
        spec=the_spec,
        base_path='/',
    )

    assert resolve_handler.call_count == len(the_spec.operations)

    assert add_route_spy.call_count == len(the_spec.operations)
    assert len(the_app.root_app.router.resources()) == len(the_spec.operations)
def test_app_add_with_custom_base_path(mocker: ptm.MockFixture) -> None:
    server_url = '/api/v1/'
    arg_base_path = '/'

    spec_one = mocker.stub()
    spec_one.servers = [
        model.OASServer(url=server_url, variables={}),
    ]

    apply_spec = mocker.patch('axion.plugins._aiohttp._apply_specification')
    get_base_path = mocker.patch('axion.plugins._aiohttp._get_base_path')

    the_app = app.AioHttpPlugin(configuration=mocker.ANY)
    the_app.add_api(spec_one, base_path=arg_base_path)

    assert 1 == len(the_app.api_base_paths)
    assert arg_base_path in the_app.api_base_paths
    assert server_url not in the_app.api_base_paths

    apply_spec.assert_any_call(
        for_app=the_app.root_app,
        spec=spec_one,
    )
    get_base_path.assert_not_called()