Skip to content

Commit

Permalink
Adding basic integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-zywicki committed Nov 10, 2021
1 parent 5e44d44 commit d02b1a7
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 0 deletions.
7 changes: 7 additions & 0 deletions asynction/default_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def default_on_connect_handler(*args, **kwargs):
"""Injected into api when security is specified by no connect handler is provided"""

pass


DEFAULT_ON_CONNECT_HANDLER = "asynction.default_handlers.default_on_connect_handler"
9 changes: 9 additions & 0 deletions asynction/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from flask import Flask
from flask_socketio import SocketIO

from asynction.default_handlers import DEFAULT_ON_CONNECT_HANDLER
from asynction.exceptions import ValidationException
from asynction.loader import load_handler
from asynction.playground_docs import make_docs_blueprint
Expand Down Expand Up @@ -226,6 +227,14 @@ def _register_handlers(

self.on_event(message.name, handler, namespace)

if server_security is not None and (
channel.x_handlers is None or channel.x_handlers.connect is None
):
if channel.x_handlers is None:
channel.x_handlers = ChannelHandlers()
if channel.x_handlers.connect is None:
channel.x_handlers.connect = DEFAULT_ON_CONNECT_HANDLER

if channel.x_handlers is not None:
self._register_namespace_handlers(
namespace,
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ class FixturePaths(NamedTuple):
simple: Path
echo: Path
simple_with_servers: Path
security: Path
security_oauth2: Path


paths = FixturePaths(
simple=Path(__file__).parent.joinpath("simple.yml"),
echo=Path(__file__).parent.joinpath("echo.yml"),
simple_with_servers=Path(__file__).parent.joinpath("simple_with_servers.yml"),
security=Path(__file__).parent.joinpath("security.yaml"),
security_oauth2=Path(__file__).parent.joinpath("security_oauth2.yaml"),
)
45 changes: 45 additions & 0 deletions tests/fixtures/handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import base64
from typing import Any
from typing import Mapping
from typing import Optional
from typing import Sequence

from flask import request
from flask_socketio import emit
Expand Down Expand Up @@ -52,3 +56,44 @@ def authenticated_connect() -> None:
def echo_failed_validation(e: Exception) -> None:
if isinstance(e, ValidationException):
emit("echo errors", "Incoming message failed validation")


def basic_info(
username: str, password: str, required_scopes: Optional[Sequence[str]] = None
) -> Mapping:
if username != "username" or password != "password":
raise ConnectionRefusedError("Invalid username or password")

return dict(user=username, scopes=list(required_scopes))


def bearer_info(
token: str,
required_scopes: Optional[Sequence[str]] = None,
bearer_format: Optional[str] = None,
) -> Mapping:
username, password = base64.b64decode(token).decode().split(":")
if username != "username" or password != "password" or bearer_format != "test":
raise ConnectionRefusedError("Invalid username or password")

return dict(user=username, scopes=list(required_scopes))


def api_key_info(
token: str,
required_scopes: Optional[Sequence[str]] = None,
bearer_format: Optional[str] = None,
) -> Mapping:
username, password = base64.b64decode(token).decode().split(":")
if username != "username" or password != "password":
raise ConnectionRefusedError("Invalid username or password")

return dict(user=username, scopes=list(required_scopes))


def token_info(token: str) -> Mapping:
username, password = base64.b64decode(token).decode().split(":")
if username != "username" or password != "password":
raise ConnectionRefusedError("Invalid username or password")

return dict(user=username, scopes=["a", "b"])
39 changes: 39 additions & 0 deletions tests/fixtures/security.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
asyncapi: 2.0.0
info:
title: Test
version: 1.0.0
servers:
test:
protocol: wss
url: 127.0.0.1/socket.io
security:
- basic: []
- bearer: ["a"]
- apiKey: ["a"]
channels:
/:
subscribe:
message:
$ref: "#/components/messages/Test"
components:
messages:
Test:
name: test
payload:
type: string

securitySchemes:
basic:
type: http
scheme: basic
x-basicInfoFunc: tests.fixtures.handlers.basic_info
bearer:
type: http
scheme: bearer
bearerFormat: test
x-apiKeyInfoFunc: tests.fixtures.handlers.bearer_info
apiKey:
type: httpApiKey
in: query
name: api_key
x-apiKeyInfoFunc: tests.fixtures.handlers.api_key_info
32 changes: 32 additions & 0 deletions tests/fixtures/security_oauth2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
asyncapi: 2.0.0
info:
title: Test
version: 1.0.0
servers:
test:
protocol: wss
url: 127.0.0.1/socket.io
security:
- oauth2: ["a"]
channels:
/:
subscribe:
message:
$ref: "#/components/messages/Test"
components:
messages:
Test:
name: test
payload:
type: string

securitySchemes:
oauth2:
type: oauth2
flows:
implicit:
authorizationUrl: test
scopes:
a: "Test A"
b: "Test B"
x-tokenInfoFunc: tests.fixtures.handlers.token_info
135 changes: 135 additions & 0 deletions tests/integration/test_server.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import base64
from enum import Enum
from typing import Callable

Expand Down Expand Up @@ -313,3 +314,137 @@ def test_docs_raw_specification_endpoint(

with fixture_paths.simple.open() as f:
assert resolve_references(yaml.safe_load(f.read())) == resp.json


@pytest.mark.parametrize(
argnames="factory_fixture",
argvalues=[FactoryFixture.ASYNCTION_SOCKET_IO],
ids=["server"],
)
def test_client_fails_to_connect_with_no_auth(
factory_fixture: FactoryFixture,
flask_app: Flask,
fixture_paths: FixturePaths,
request: pytest.FixtureRequest,
):
server_factory: AsynctionFactory = request.getfixturevalue(factory_fixture.value)

socketio_server = server_factory(
spec_path=fixture_paths.security, server_name="test"
)
flask_test_client = flask_app.test_client()

with pytest.raises(ConnectionRefusedError):
socketio_test_client = socketio_server.test_client(
flask_app, flask_test_client=flask_test_client
)

assert socketio_test_client.is_connected() is False


@pytest.mark.parametrize(
argnames="factory_fixture",
argvalues=[FactoryFixture.ASYNCTION_SOCKET_IO],
ids=["server"],
)
def test_client_connects_with_http_basic_auth(
factory_fixture: FactoryFixture,
flask_app: Flask,
fixture_paths: FixturePaths,
request: pytest.FixtureRequest,
):
server_factory: AsynctionFactory = request.getfixturevalue(factory_fixture.value)

socketio_server = server_factory(
spec_path=fixture_paths.security, server_name="test"
)
flask_test_client = flask_app.test_client()

basic_auth = base64.b64encode("username:password".encode()).decode()
headers = {"Authorization": f"basic {basic_auth}"}
socketio_test_client = socketio_server.test_client(
flask_app, flask_test_client=flask_test_client, headers=headers
)

assert socketio_test_client.is_connected() is True


@pytest.mark.parametrize(
argnames="factory_fixture",
argvalues=[FactoryFixture.ASYNCTION_SOCKET_IO],
ids=["server"],
)
def test_client_connects_with_http_bearer_auth(
factory_fixture: FactoryFixture,
flask_app: Flask,
fixture_paths: FixturePaths,
request: pytest.FixtureRequest,
):
server_factory: AsynctionFactory = request.getfixturevalue(factory_fixture.value)

socketio_server = server_factory(
spec_path=fixture_paths.security, server_name="test"
)
flask_test_client = flask_app.test_client()

basic_auth = base64.b64encode("username:password".encode()).decode()
headers = {"Authorization": f"bearer {basic_auth}"}
socketio_test_client = socketio_server.test_client(
flask_app, flask_test_client=flask_test_client, headers=headers
)

assert socketio_test_client.is_connected() is True


@pytest.mark.parametrize(
argnames="factory_fixture",
argvalues=[FactoryFixture.ASYNCTION_SOCKET_IO],
ids=["server"],
)
def test_client_connects_with_http_api_key_auth(
factory_fixture: FactoryFixture,
flask_app: Flask,
fixture_paths: FixturePaths,
request: pytest.FixtureRequest,
):
server_factory: AsynctionFactory = request.getfixturevalue(factory_fixture.value)

socketio_server = server_factory(
spec_path=fixture_paths.security, server_name="test"
)
flask_test_client = flask_app.test_client()

basic_auth = base64.b64encode("username:password".encode()).decode()
query = f"api_key={basic_auth}"
socketio_test_client = socketio_server.test_client(
flask_app, flask_test_client=flask_test_client, query_string=query
)

assert socketio_test_client.is_connected() is True


@pytest.mark.parametrize(
argnames="factory_fixture",
argvalues=[FactoryFixture.ASYNCTION_SOCKET_IO],
ids=["server"],
)
def test_client_connects_with_oauth2(
factory_fixture: FactoryFixture,
flask_app: Flask,
fixture_paths: FixturePaths,
request: pytest.FixtureRequest,
):
server_factory: AsynctionFactory = request.getfixturevalue(factory_fixture.value)

socketio_server = server_factory(
spec_path=fixture_paths.security_oauth2, server_name="test"
)
flask_test_client = flask_app.test_client()

basic_auth = base64.b64encode("username:password".encode()).decode()
headers = {"Authorization": f"bearer {basic_auth}"}
socketio_test_client = socketio_server.test_client(
flask_app, flask_test_client=flask_test_client, headers=headers
)

assert socketio_test_client.is_connected() is True

0 comments on commit d02b1a7

Please sign in to comment.