Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Replace Bottle with FastAPI #668

Merged
merged 14 commits into from
Apr 26, 2024
Merged
4 changes: 2 additions & 2 deletions tests/integration/async_push_test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(self, url) -> None:
}

async def connect(self, connection_port: int | None = None) -> None:
"""Establish a websocket connection to localhost at the provided `connection_port`.
"""Establish a websocket connection to localhost (127.0.0.1) at provided `connection_port`.

Parameters
----------
Expand All @@ -59,7 +59,7 @@ async def connect(self, connection_port: int | None = None) -> None:
"""
url: str = self.url
if connection_port: # pragma: no cover
url = f"ws://localhost:{connection_port}/"
url = f"ws://127.0.0.1:{connection_port}/"
self.ws = await websockets.connect(uri=url, extra_headers=self.headers)

async def ws_server_send(self, message: dict) -> None:
Expand Down
1 change: 1 addition & 0 deletions tests/integration/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
``VERSION`` is a 2-digit 0-padded number, starting at 01 for Topic messages.

"""

from __future__ import absolute_import

import base64
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/dual_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"db_settings": "{\"message_table\": \"message_int_test\",\"router_table\": \"router_int_test\"}",
"dsn": "http://localhost:8000/"
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was removing this last \n intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My auto format for some reason did so! Probably an IDE "feature". Is there a preference one way or another? Happy to change it if we prefer a trailing line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no worries. It's a JSON file so not super critical. Just curious if there was some different reason.

59 changes: 35 additions & 24 deletions tests/integration/test_integration_all_rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
from unittest import SkipTest
from urllib.parse import urlparse

import bottle
import ecdsa
import httpx
import psutil
import pytest
import twisted.internet.base
import uvicorn
import websockets
from cryptography.fernet import Fernet
from fastapi import FastAPI, Request
from jose import jws

from .async_push_test_client import AsyncPushTestClient, ClientMessageType
Expand All @@ -37,7 +38,7 @@
get_router_table,
)

app = bottle.Bottle()
app = FastAPI()
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)

Expand Down Expand Up @@ -94,7 +95,7 @@ def get_free_port() -> int:
"""Get free port."""
port: int
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
s.bind(("localhost", 0))
s.bind(("127.0.0.1", 0))
address, port = s.getsockname()
s.close()
return port
Expand Down Expand Up @@ -138,9 +139,9 @@ def get_db_settings() -> str | dict[str, str | int | float] | None:
creation of the local server.
"""
CONNECTION_CONFIG: dict[str, Any] = dict(
hostname="localhost",
hostname="127.0.0.1",
port=CONNECTION_PORT,
endpoint_hostname="localhost",
endpoint_hostname="127.0.0.1",
endpoint_port=ENDPOINT_PORT,
router_port=ROUTER_PORT,
endpoint_scheme="http",
Expand Down Expand Up @@ -171,7 +172,7 @@ def get_db_settings() -> str | dict[str, str | int | float] | None:
auto_ping_timeout=10.0,
close_handshake_timeout=5,
max_connections=5000,
megaphone_api_url="http://localhost:{port}/v1/broadcasts".format(port=MOCK_SERVER_PORT),
megaphone_api_url=f"http://127.0.0.1:{MOCK_SERVER_PORT}/v1/broadcasts/",
megaphone_api_token=MOCK_MP_TOKEN,
megaphone_poll_interval=1,
)
Expand All @@ -181,7 +182,7 @@ def get_db_settings() -> str | dict[str, str | int | float] | None:
creation of the local server.
"""
ENDPOINT_CONFIG = dict(
host="localhost",
host="127.0.0.1",
port=ENDPOINT_PORT,
router_table_name=ROUTER_TABLE,
message_table_name=MESSAGE_TABLE,
Expand Down Expand Up @@ -291,17 +292,17 @@ async def wrapper(self, *args, **kwargs):


@app.get("/v1/broadcasts")
def broadcast_handler():
async def broadcast_handler(request: Request) -> dict[str, dict[str, str]]:
"""Broadcast handler setup."""
assert bottle.request.headers["Authorization"] == MOCK_MP_TOKEN
assert request.headers["Authorization"] == MOCK_MP_TOKEN
MOCK_MP_POLLED.set()
return dict(broadcasts=MOCK_MP_SERVICES)


@app.post("/api/1/envelope/")
def sentry_handler() -> dict[str, str]:
async def sentry_handler(request: Request) -> dict[str, str]:
"""Sentry handler configuration."""
headers, item_headers, payload = bottle.request.body.read().splitlines()
_headers, _item_headers, payload = (await request.body()).splitlines()
MOCK_SENTRY_QUEUE.put(json.loads(payload))
return {"id": "fc6d8c0c43fc4630ad850ee518f1b9d0"}

Expand Down Expand Up @@ -365,7 +366,7 @@ def setup_bt():
# Will look at future replacement when moving to Docker.
# https://bandit.readthedocs.io/en/latest/plugins/b603_subprocess_without_shell_equals_true.html
BT_PROCESS = subprocess.Popen("gcloud beta emulators bigtable start".split(" ")) # nosec
os.environ["BIGTABLE_EMULATOR_HOST"] = "localhost:8086"
os.environ["BIGTABLE_EMULATOR_HOST"] = "127.0.0.1:8086"
try:
BT_DB_SETTINGS = os.environ.get(
"BT_DB_SETTINGS",
Expand Down Expand Up @@ -412,16 +413,23 @@ def setup_dynamodb():
get_router_table(boto_resource, ROUTER_TABLE)


def run_fastapi_app(host, port):
"""Run FastAPI app with uvicorn."""
uvicorn.run(app, host=host, port=port, log_level="debug")


def setup_mock_server():
"""Set up mock server."""
global MOCK_SERVER_THREAD

MOCK_SERVER_THREAD = Thread(target=app.run, kwargs=dict(port=MOCK_SERVER_PORT, debug=True))
MOCK_SERVER_THREAD = Thread(
target=run_fastapi_app, kwargs=dict(host="127.0.0.1", port=MOCK_SERVER_PORT)
)
MOCK_SERVER_THREAD.daemon = True
MOCK_SERVER_THREAD.start()

# Sentry API mock
os.environ["SENTRY_DSN"] = "http://foo:bar@localhost:{}/1".format(MOCK_SERVER_PORT)
os.environ["SENTRY_DSN"] = f"http://foo:bar@127.0.0.1:{MOCK_SERVER_PORT}/1"


def setup_connection_server(connection_binary):
Expand Down Expand Up @@ -551,6 +559,10 @@ def setup_module():

setup_mock_server()

# NOTE: This sleep is required to ensure that autopush does not ping the Megaphone
# v1/broadcasts endpoint before it is ready.
time.sleep(1)

log.debug(f"🐍🟢 Rust Log: {RUST_LOG}")
os.environ["RUST_LOG"] = RUST_LOG
connection_binary = get_rust_binary_path(CONNECTION_BINARY)
Expand Down Expand Up @@ -609,8 +621,8 @@ async def quick_register(self):
"""Perform a connection initialization, which includes a new connection,
`hello`, and channel registration.
"""
log.debug(f"🐍#### Connecting to ws://localhost:{CONNECTION_PORT}/")
client = AsyncPushTestClient(f"ws://localhost:{CONNECTION_PORT}/")
log.debug(f"🐍#### Connecting to ws://127.0.0.1:{CONNECTION_PORT}/")
client = AsyncPushTestClient(f"ws://127.0.0.1:{CONNECTION_PORT}/")
await client.connect()
await client.hello()
await client.register()
Expand All @@ -624,7 +636,7 @@ async def shut_down(self, client=None):

@property
def _ws_url(self):
return f"ws://localhost:{CONNECTION_PORT}/"
return f"ws://127.0.0.1:{CONNECTION_PORT}/"

@pytest.mark.asyncio
@max_logs(conn=4)
Expand All @@ -642,7 +654,7 @@ async def test_sentry_output_autoconnect(self):

# LogCheck does throw an error every time
async with httpx.AsyncClient() as httpx_client:
await httpx_client.get(f"http://localhost:{CONNECTION_PORT}/v1/err/crit", timeout=30)
await httpx_client.get(f"http://127.0.0.1:{CONNECTION_PORT}/v1/err/crit", timeout=30)

event1 = MOCK_SENTRY_QUEUE.get(timeout=5)
# NOTE: this timeout increased to 5 seconds as was yielding
Expand Down Expand Up @@ -1309,14 +1321,14 @@ async def test_with_key(self):
"""Test getting a locked subscription with a valid VAPID public key."""
private_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
claims = {
"aud": f"http://localhost:{ENDPOINT_PORT}",
"aud": f"http://127.0.0.1:{ENDPOINT_PORT}",
"exp": int(time.time()) + 86400,
"sub": "[email protected]",
}
vapid = _get_vapid(private_key, claims)
pk_hex = vapid["crypto-key"]
chid = str(uuid.uuid4())
client = AsyncPushTestClient(f"ws://localhost:{CONNECTION_PORT}/")
client = AsyncPushTestClient(f"ws://127.0.0.1:{CONNECTION_PORT}/")
await client.connect()
await client.hello()
await client.register(channel_id=chid, key=pk_hex)
Expand All @@ -1336,7 +1348,7 @@ async def test_with_key(self):
async def test_with_bad_key(self):
"""Test that a message registration request with bad VAPID public key is rejected."""
chid = str(uuid.uuid4())
client = AsyncPushTestClient(f"ws://localhost:{CONNECTION_PORT}/")
client = AsyncPushTestClient(f"ws://127.0.0.1:{CONNECTION_PORT}/")
await client.connect()
await client.hello()
result = await client.register(channel_id=chid, key="af1883%&!@#*(", status=400)
Expand Down Expand Up @@ -1425,7 +1437,7 @@ def tearDown(self):
async def quick_register(self, connection_port=None):
"""Connect and register client."""
conn_port = connection_port or MP_CONNECTION_PORT
client = AsyncPushTestClient(f"ws://localhost:{conn_port}/")
client = AsyncPushTestClient(f"ws://127.0.0.1:{conn_port}/")
await client.connect()
await client.hello()
await client.register()
Expand All @@ -1439,7 +1451,7 @@ async def shut_down(self, client=None):

@property
def _ws_url(self):
return f"ws://localhost:{MP_CONNECTION_PORT}/"
return f"ws://127.0.0.1:{MP_CONNECTION_PORT}/"

@pytest.mark.asyncio
async def test_broadcast_update_on_connect(self):
Expand All @@ -1448,7 +1460,6 @@ async def test_broadcast_update_on_connect(self):
MOCK_MP_SERVICES = {"kinto:123": "ver1"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

old_ver = {"kinto:123": "ver0"}
client = AsyncPushTestClient(self._ws_url)
await client.connect()
Expand Down
1 change: 1 addition & 0 deletions tests/load/locustfiles/args.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Load test arguments."""

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Expand Down
1 change: 1 addition & 0 deletions tests/load/locustfiles/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Custom exceptions for load tests."""

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Expand Down
1 change: 1 addition & 0 deletions tests/load/locustfiles/locustfile.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Performance test module."""

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Expand Down
Loading