Skip to content

Commit bb36b68

Browse files
authored
Merge branch 'main' into feat/serve-env-doc
2 parents f5bae61 + 30af01f commit bb36b68

File tree

6 files changed

+56
-25
lines changed

6 files changed

+56
-25
lines changed

LICENSE

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Copyright 2025 Bullet Train Ltd. A UK company.
2+
3+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4+
5+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6+
7+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8+
9+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10+
11+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[project]
22
dynamic = ["version"]
33
name = "edge-proxy"
4+
license = { file = "LICENSE" }
45
dependencies = [
56
"fastapi",
67
"flagsmith-flag-engine>5",

src/edge_proxy/settings.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,15 @@
88

99
import structlog
1010

11-
from pydantic import AliasChoices, BaseModel, HttpUrl, IPvAnyAddress, Field
11+
from pydantic import AliasChoices, BaseModel, HttpUrl, IPvAnyAddress, Field, constr
1212

1313
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
1414

15-
1615
CONFIG_PATH = os.environ.get(
1716
"CONFIG_PATH",
1817
default="config.json",
1918
)
2019

21-
2220
logger = structlog.get_logger()
2321

2422

@@ -65,8 +63,8 @@ def json_config_settings_source() -> dict[str, Any]:
6563

6664

6765
class EnvironmentKeyPair(BaseModel):
68-
server_side_key: str
69-
client_side_key: str
66+
server_side_key: constr(pattern=r"ser\.*", strip_whitespace=True)
67+
client_side_key: constr(min_length=1, strip_whitespace=True)
7068

7169

7270
class EndpointCacheSettings(BaseModel):
@@ -105,14 +103,7 @@ class HealthCheckSettings(BaseModel):
105103

106104

107105
class AppSettings(BaseModel):
108-
environment_key_pairs: list[EnvironmentKeyPair] = Field(
109-
default_factory=lambda: [
110-
EnvironmentKeyPair(
111-
server_side_key="ser.environment_key",
112-
client_side_key="environment_key",
113-
)
114-
]
115-
)
106+
environment_key_pairs: list[EnvironmentKeyPair]
116107
api_url: HttpUrl = HttpUrl("https://edge.api.flagsmith.com/api/v1")
117108
api_poll_frequency_seconds: int = Field(
118109
default=10,

tests/conftest.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,15 @@ def environment_1_feature_states_response_list_response_with_identity_override(
5151

5252

5353
@pytest.fixture(autouse=True)
54-
def skip_json_config_settings_source(mocker: MockerFixture) -> None:
55-
mocker.patch("edge_proxy.settings.json_config_settings_source", dict)
54+
def mock_settings(mocker: MockerFixture) -> None:
55+
mock_config = {
56+
"environment_key_pairs": [
57+
{"server_side_key": "ser.abc123", "client_side_key": "def456"}
58+
]
59+
}
60+
mocker.patch(
61+
"edge_proxy.settings.json_config_settings_source", return_value=mock_config
62+
)
5663

5764

5865
@pytest.fixture

tests/test_server.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,8 @@ def test_health_check_returns_200_if_cache_is_stale_and_health_check_configured_
5959
client: TestClient,
6060
) -> None:
6161
# Given
62-
settings = AppSettings(
63-
health_check=HealthCheckSettings(environment_update_grace_period_seconds=None)
64-
)
65-
mocker.patch("edge_proxy.server.settings", settings)
62+
health_check = HealthCheckSettings(environment_update_grace_period_seconds=None)
63+
mocker.patch("edge_proxy.server.settings.health_check", health_check)
6664

6765
last_updated_at = datetime.now() - timedelta(days=10)
6866
mocked_environment_service = mocker.patch("edge_proxy.server.environment_service")

tests/test_settings.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
1-
import typing
1+
from typing import Any, Optional
22

33
import pytest
44
from pytest_mock import MockerFixture
5+
from pydantic import ValidationError
56

6-
from edge_proxy.settings import get_settings
7+
from edge_proxy.settings import get_settings, AppSettings
8+
9+
10+
@pytest.mark.parametrize(
11+
"client_side_key,server_side_key,expected_exception",
12+
[
13+
("abc123", "ser.456", None),
14+
("abc123", "456", ValidationError),
15+
("abc123", "", ValidationError),
16+
("", "ser.456", ValidationError),
17+
],
18+
)
19+
def test_client_side_key_validation(
20+
client_side_key: str, server_side_key: str, expected_exception: Optional[Exception]
21+
) -> None:
22+
try:
23+
AppSettings(
24+
environment_key_pairs=[
25+
{"server_side_key": server_side_key, "client_side_key": client_side_key}
26+
]
27+
)
28+
except expected_exception:
29+
pass
730

831

932
@pytest.mark.parametrize(
@@ -12,7 +35,7 @@
1235
(
1336
{
1437
"environment_key_pairs": [
15-
{"server_side_key": "abc123", "client_side_key": "ser.def456"}
38+
{"server_side_key": "ser.abc123", "client_side_key": "def456"}
1639
],
1740
"api_poll_frequency": 10,
1841
"api_poll_timeout": 10,
@@ -23,7 +46,7 @@
2346
(
2447
{
2548
"environment_key_pairs": [
26-
{"server_side_key": "abc123", "client_side_key": "ser.def456"}
49+
{"server_side_key": "ser.abc123", "client_side_key": "def456"}
2750
],
2851
"api_poll_frequency_seconds": 10,
2952
"api_poll_timeout_seconds": 10,
@@ -35,8 +58,8 @@
3558
)
3659
def test_settings_are_loaded_correctly(
3760
mocker: MockerFixture,
38-
config_file_json: dict[str, typing.Any],
39-
expected_config: dict[str, typing.Any],
61+
config_file_json: dict[str, Any],
62+
expected_config: dict[str, Any],
4063
) -> None:
4164
"""
4265
Parametrized test which accepts a raw json config file, and a dictionary representing the

0 commit comments

Comments
 (0)