Skip to content

Commit d560b9a

Browse files
authored
Functional tests for /azure_byod (#589)
* Functional tests for /azure_byod * Split tests into two * Refactor + README
1 parent 9b182ab commit d560b9a

File tree

12 files changed

+445
-85
lines changed

12 files changed

+445
-85
lines changed

code/tests/functional/backend_api/app_config.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import logging
22
import os
3-
from typing import Any, Dict
43

54

65
class AppConfig:
7-
config: Dict[str, Any] = {
6+
config: dict[str, str | None] = {
87
"AZURE_SPEECH_SERVICE_KEY": "some-azure-speech-service-key",
98
"AZURE_SPEECH_SERVICE_REGION": "some-azure-speech-service-region",
109
"APPINSIGHTS_ENABLED": "False",
1110
"AZURE_OPENAI_API_KEY": "some-azure-openai-api-key",
11+
"AZURE_OPENAI_API_VERSION": "2024-02-01",
12+
"AZURE_SEARCH_INDEX": "some-azure-search-index",
1213
"AZURE_SEARCH_KEY": "some-azure-search-key",
1314
"AZURE_CONTENT_SAFETY_KEY": "some-content_safety-key",
1415
"AZURE_OPENAI_EMBEDDING_MODEL": "some-embedding-model",
1516
"AZURE_OPENAI_MODEL": "some-openai-model",
1617
"AZURE_SEARCH_CONVERSATIONS_LOG_INDEX": "some-log-index",
18+
"AZURE_OPENAI_STREAM": "True",
1719
"LOAD_CONFIG_FROM_BLOB_STORAGE": "False",
1820
"TIKTOKEN_CACHE_DIR": f"{os.path.dirname(os.path.realpath(__file__))}/resources",
21+
# These values are set directly within EnvHelper, adding them here ensures
22+
# that they are removed from the environment when remove_from_environment() runs
23+
"OPENAI_API_TYPE": None,
24+
"OPENAI_API_KEY": None,
25+
"OPENAI_API_VERSION": None,
1926
}
2027

21-
def __init__(self, config_overrides: Dict[str, Any] = {}) -> None:
28+
def __init__(self, config_overrides: dict[str, str | None] = {}) -> None:
2229
self.config = self.config | config_overrides
2330

24-
def set(self, key: str, value: Any) -> None:
31+
def set(self, key: str, value: str | None) -> None:
2532
self.config[key] = value
2633

27-
def get(self, key: str) -> Any:
34+
def get(self, key: str) -> str | None:
2835
return self.config[key]
2936

30-
def get_all(self) -> Dict[str, Any]:
37+
def get_all(self) -> dict[str, str | None]:
3138
return self.config
3239

3340
def apply_to_environment(self) -> None:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import logging
2+
import socket
3+
import threading
4+
import time
5+
import requests
6+
from threading import Thread
7+
from create_app import create_app
8+
9+
10+
def start_app(app_port: int) -> Thread:
11+
logging.info(f"Starting application on port {app_port}")
12+
app = create_app()
13+
app_process = threading.Thread(target=lambda: app.run(port=app_port), daemon=True)
14+
app_process.start()
15+
wait_for_app(app_port)
16+
logging.info("Application started")
17+
return app_process
18+
19+
20+
def wait_for_app(port: int, initial_check_delay: int = 2):
21+
attempts = 0
22+
time.sleep(initial_check_delay)
23+
while attempts < 10:
24+
try:
25+
response = requests.get(f"http://localhost:{port}/api/config")
26+
if response.status_code == 200:
27+
return
28+
except Exception:
29+
pass
30+
31+
attempts += 1
32+
time.sleep(1)
33+
34+
raise Exception("App failed to start")
35+
36+
37+
def get_free_port() -> int:
38+
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
39+
s.bind(("localhost", 0))
40+
_, port = s.getsockname()
41+
s.close()
42+
return port

code/tests/functional/backend_api/conftest.py

-79
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
1-
import logging
2-
import socket
31
import ssl
4-
import threading
5-
import time
62
import pytest
73
from pytest_httpserver import HTTPServer
8-
import requests
94
from tests.functional.backend_api.app_config import AppConfig
10-
from threading import Thread
115
import trustme
12-
from create_app import create_app
136

147

158
@pytest.fixture(scope="session")
@@ -43,42 +36,6 @@ def httpclient_ssl_context(ca):
4336
return ssl.create_default_context(cafile=ca_temp_path)
4437

4538

46-
@pytest.fixture(scope="session")
47-
def app_port() -> int:
48-
logging.info("Getting free port")
49-
return get_free_port()
50-
51-
52-
@pytest.fixture(scope="session")
53-
def app_url(app_port: int) -> str:
54-
return f"http://localhost:{app_port}"
55-
56-
57-
@pytest.fixture(scope="session")
58-
def app_config(make_httpserver, ca):
59-
logging.info("Creating APP CONFIG")
60-
with ca.cert_pem.tempfile() as ca_temp_path:
61-
app_config = AppConfig(
62-
{
63-
"AZURE_OPENAI_ENDPOINT": f"https://localhost:{make_httpserver.port}",
64-
"AZURE_SEARCH_SERVICE": f"https://localhost:{make_httpserver.port}",
65-
"AZURE_CONTENT_SAFETY_ENDPOINT": f"https://localhost:{make_httpserver.port}",
66-
"SSL_CERT_FILE": ca_temp_path,
67-
"CURL_CA_BUNDLE": ca_temp_path,
68-
}
69-
)
70-
logging.info(f"Created app config: {app_config.get_all()}")
71-
yield app_config
72-
73-
74-
@pytest.fixture(scope="session", autouse=True)
75-
def manage_app(app_port: int, app_config: AppConfig):
76-
app_config.apply_to_environment()
77-
start_app(app_port)
78-
yield
79-
app_config.remove_from_environment()
80-
81-
8239
@pytest.fixture(scope="function", autouse=True)
8340
def setup_default_mocking(httpserver: HTTPServer, app_config: AppConfig):
8441
httpserver.expect_request(
@@ -154,39 +111,3 @@ def setup_default_mocking(httpserver: HTTPServer, app_config: AppConfig):
154111
yield
155112

156113
httpserver.check()
157-
158-
159-
def start_app(app_port: int) -> Thread:
160-
logging.info(f"Starting application on port {app_port}")
161-
app = create_app()
162-
app_process = threading.Thread(target=lambda: app.run(port=app_port))
163-
app_process.daemon = True
164-
app_process.start()
165-
wait_for_app(app_port)
166-
logging.info("Application started")
167-
return app_process
168-
169-
170-
def wait_for_app(port: int, initial_check_delay: int = 10):
171-
attempts = 0
172-
time.sleep(initial_check_delay)
173-
while attempts < 10:
174-
try:
175-
response = requests.get(f"http://localhost:{port}/api/config")
176-
if response.status_code == 200:
177-
return
178-
except Exception:
179-
pass
180-
181-
attempts += 1
182-
time.sleep(1)
183-
184-
raise Exception("App failed to start")
185-
186-
187-
def get_free_port() -> int:
188-
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
189-
s.bind(("localhost", 0))
190-
_, port = s.getsockname()
191-
s.close()
192-
return port
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Backend API Tests
2+
3+
At present, there are two sets of tests: `with_data` and `without_data`.
4+
Each set of tests starts its own instance of the backend API on a different port.
5+
The difference between the two is the environment variables, namely the lack of the
6+
`AZURE_SEARCH_SERVICE` variable for the `without_data` tests.
7+
8+
When adding new tests, first check to see if it is possible to add the tests to an
9+
existing set of tests, rather than creating a new set, as this removes the need for
10+
starting up a new instance of the application on another port.
11+
12+
New environment variables common to all tests can be directly added to the `config`
13+
dict in [app_config.py](../app_config.py), while variables only needed for one set
14+
of tests can be added to the `app_config` fixture in the respective `conftest.py`
15+
file, e.g. [./with_data/conftest.py](./with_data/conftest.py).
16+
17+
```py
18+
@pytest.fixture(scope="package")
19+
def app_config(make_httpserver, ca):
20+
logging.info("Creating APP CONFIG")
21+
with ca.cert_pem.tempfile() as ca_temp_path:
22+
app_config = AppConfig(
23+
{
24+
"AZURE_OPENAI_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
25+
"AZURE_SEARCH_SERVICE": f"https://localhost:{make_httpserver.port}/",
26+
"AZURE_CONTENT_SAFETY_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
27+
"SSL_CERT_FILE": ca_temp_path,
28+
"CURL_CA_BUNDLE": ca_temp_path,
29+
"NEW_ENV_VAR": "VALUE",
30+
}
31+
)
32+
logging.info(f"Created app config: {app_config.get_all()}")
33+
yield app_config
34+
```
35+
36+
To remove an environment variable from the default defined in the `AppConfig` class,
37+
set its value to `None`.
38+
39+
```py
40+
@pytest.fixture(scope="package")
41+
def app_config(make_httpserver, ca):
42+
logging.info("Creating APP CONFIG")
43+
with ca.cert_pem.tempfile() as ca_temp_path:
44+
app_config = AppConfig(
45+
{
46+
"AZURE_OPENAI_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
47+
"AZURE_SEARCH_SERVICE": f"https://localhost:{make_httpserver.port}/",
48+
"AZURE_CONTENT_SAFETY_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
49+
"SSL_CERT_FILE": ca_temp_path,
50+
"CURL_CA_BUNDLE": ca_temp_path,
51+
"ENV_VAR_TO_REMOVE": None,
52+
}
53+
)
54+
logging.info(f"Created app config: {app_config.get_all()}")
55+
yield app_config
56+
```

code/tests/functional/backend_api/tests/with_data/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import logging
2+
import pytest
3+
from tests.functional.backend_api.app_config import AppConfig
4+
from tests.functional.backend_api.common import get_free_port, start_app
5+
6+
7+
@pytest.fixture(scope="package")
8+
def app_port() -> int:
9+
logging.info("Getting free port")
10+
return get_free_port()
11+
12+
13+
@pytest.fixture(scope="package")
14+
def app_url(app_port: int) -> str:
15+
return f"http://localhost:{app_port}"
16+
17+
18+
@pytest.fixture(scope="package")
19+
def app_config(make_httpserver, ca):
20+
logging.info("Creating APP CONFIG")
21+
with ca.cert_pem.tempfile() as ca_temp_path:
22+
app_config = AppConfig(
23+
{
24+
"AZURE_OPENAI_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
25+
"AZURE_SEARCH_SERVICE": f"https://localhost:{make_httpserver.port}/",
26+
"AZURE_CONTENT_SAFETY_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
27+
"SSL_CERT_FILE": ca_temp_path,
28+
"CURL_CA_BUNDLE": ca_temp_path,
29+
}
30+
)
31+
logging.info(f"Created app config: {app_config.get_all()}")
32+
yield app_config
33+
34+
35+
@pytest.fixture(scope="package", autouse=True)
36+
def manage_app(app_port: int, app_config: AppConfig):
37+
app_config.apply_to_environment()
38+
start_app(app_port)
39+
yield
40+
app_config.remove_from_environment()

0 commit comments

Comments
 (0)