Skip to content

Commit bcbf3be

Browse files
refactor(test): use dynamic ports for testcontainers and simplify config access
1 parent 1dbf452 commit bcbf3be

14 files changed

Lines changed: 297 additions & 241 deletions

.env.test

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ FASTAPI__DOCS_URL="/docs"
1515
FASTAPI__RE_DOCS_URL="/redoc"
1616
FASTAPI__SWAGGER_UI_PARAMS={}
1717

18-
# Redis Configuration (RedisConfig)
18+
# Testcontainers Configuration
19+
TESTCONTAINERS_RYUK_CONTAINER_IMAGE=testcontainers/ryuk:0.14.0
20+
21+
# Redis Configuration
22+
REDIS__IMAGE=redis:8.2.3-alpine
1923
REDIS__MASTER_HOST=localhost:6379
20-
REDIS__IMAGE=redis:8.2.2-alpine
2124
REDIS__DATABASE=0
2225
REDIS__PASSWORD=test_password
2326

@@ -29,33 +32,30 @@ POSTGRES_SQLALCHEMY__DATABASE=test_db
2932
POSTGRES_SQLALCHEMY__USERNAME=test_user
3033
POSTGRES_SQLALCHEMY__PASSWORD=test_password
3134

32-
# Keycloak Configuration (KeycloakConfig)
35+
# Keycloak Configuration
36+
KEYCLOAK__IMAGE=quay.io/keycloak/keycloak:26.4.2
3337
KEYCLOAK__SERVER_URL=http://localhost:8080
34-
KEYCLOAK__IMAGE=quay.io/keycloak/keycloak:26.4.0
3538
KEYCLOAK__ADMIN_USERNAME=admin
3639
KEYCLOAK__ADMIN_PASSWORD=admin
3740
KEYCLOAK__REALM_NAME=master
3841
KEYCLOAK__CLIENT_ID=admin-cli
3942
KEYCLOAK__IS_ADMIN_MODE_ENABLED=true
40-
KEYCLOAK__ADMIN_USERNAME=admin
41-
KEYCLOAK__ADMIN_PASSWORD=admin
4243
KEYCLOAK__ADMIN_REALM_NAME=master
4344
KEYCLOAK__VERIFY_SSL=false
4445
KEYCLOAK__TIMEOUT=30
4546

46-
# Elasticsearch Configuration (ElasticsearchConfig)
47+
# Elasticsearch Configuration
48+
ELASTIC__IMAGE=elastic/elasticsearch:9.2.0
4749
ELASTIC__HOSTS=["http://localhost:9200"]
48-
ELASTIC__IMAGE=docker.elastic.co/elasticsearch/elasticsearch:9.1.5
49-
ELASTIC__PORT=9200
5050
ELASTIC__HTTP_USER_NAME=elastic
5151
ELASTIC__HTTP_PASSWORD=test_password
5252

53-
# Kafka Configuration (KafkaConfig)
53+
# Kafka Configuration
5454
KAFKA__IMAGE=confluentinc/cp-kafka:7.9.3
5555
KAFKA__BROKERS_LIST=["localhost:9092"]
5656

57-
# MinIO Configuration (MinioConfig)
58-
MINIO__ENDPOINT=localhost:9000
57+
# MinIO Configuration
5958
MINIO__IMAGE=quay.io/minio/minio:latest
59+
MINIO__ENDPOINT=localhost:9000
6060
MINIO__ACCESS_KEY=test_access_key
6161
MINIO__SECRET_KEY=test_secret_key

features/environment.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ class TestConfig(BaseConfig):
3333
KAFKA__IMAGE: str
3434
MINIO__IMAGE: str
3535
KEYCLOAK__IMAGE: str
36+
TESTCONTAINERS_RYUK_CONTAINER_IMAGE: str | None = None
3637

3738
def __init__(self, **kwargs):
3839
super().__init__(**kwargs)
3940

4041
# Configure testcontainers to use custom ryuk image
41-
ryuk_image = os.getenv("TESTCONTAINERS_RYUK_CONTAINER_IMAGE")
42-
if ryuk_image:
43-
testcontainers_config.ryuk_image = ryuk_image
42+
if self.TESTCONTAINERS_RYUK_CONTAINER_IMAGE:
43+
testcontainers_config.ryuk_image = self.TESTCONTAINERS_RYUK_CONTAINER_IMAGE
4444

4545

4646

@@ -83,12 +83,11 @@ def before_scenario(context: Context, scenario: Scenario):
8383

8484
logger.info(f"Starting scenario: {scenario.name} (ID: {scenario.id})")
8585

86-
# Assign test config to scenario context
86+
# Assign test containers to scenario context
8787
try:
88-
scenario_context.store("test_config", config)
8988
scenario_context.store("test_containers", context.test_containers)
9089
except Exception as e:
91-
logger.exception(f"Error setting test config: {e}")
90+
logger.exception(f"Error setting test containers: {e}")
9291

9392

9493

features/steps/app_utils_steps.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydantic import BaseModel, ValidationError
55
from starlette.testclient import TestClient
66

7+
from archipy.configs.base_config import BaseConfig
78
from archipy.helpers.utils.app_utils import AppUtils, FastAPIExceptionHandler, FastAPIUtils
89
from archipy.models.errors import BaseError
910
from features.test_helpers import get_current_scenario_context
@@ -12,15 +13,15 @@
1213
@given("a FastAPI app")
1314
def step_given_fastapi_app(context):
1415
scenario_context = get_current_scenario_context(context)
15-
test_config = scenario_context.get("test_config")
16+
test_config = BaseConfig.global_config()
1617
app = AppUtils.create_fastapi_app(test_config)
1718
scenario_context.store("app", app)
1819

1920

2021
@when("a FastAPI app is created")
2122
def step_when_fastapi_app_created(context):
2223
scenario_context = get_current_scenario_context(context)
23-
test_config = scenario_context.get("test_config")
24+
test_config = BaseConfig.global_config()
2425
app = AppUtils.create_fastapi_app(test_config)
2526
scenario_context.store("app", app)
2627

@@ -65,7 +66,7 @@ def step_then_check_unique_id(context, expected_id):
6566
@given("a FastAPI app with CORS configuration")
6667
def step_given_fastapi_app_with_cors(context):
6768
scenario_context = get_current_scenario_context(context)
68-
test_config = scenario_context.get("test_config")
69+
test_config = BaseConfig.global_config()
6970
app = FastAPI()
7071
FastAPIUtils.setup_cors(app, test_config)
7172
scenario_context.store("app", app)
@@ -83,7 +84,7 @@ def step_when_cors_is_setup(context):
8384
def step_then_check_cors_origin(context, expected_origin):
8485
scenario_context = get_current_scenario_context(context)
8586
middleware_stack = scenario_context.get("middleware_stack")
86-
test_config = scenario_context.get("test_config")
87+
test_config = BaseConfig.global_config()
8788
assert "CORSMiddleware" in middleware_stack
8889
assert expected_origin in test_config.FASTAPI.CORS_MIDDLEWARE_ALLOW_ORIGINS
8990

features/steps/base_config_steps.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,19 @@ def step_given_custom_base_config(context):
1212
scenario_context = get_current_scenario_context(context)
1313
config = TestConfig()
1414
BaseConfig.set_global(config)
15-
scenario_context.store("test_config", BaseConfig.global_config())
1615

1716

1817
@when("the global configuration is set")
1918
def step_when_set_global_config(context):
2019
scenario_context = get_current_scenario_context(context)
21-
test_config = scenario_context.get("test_config")
20+
test_config = BaseConfig.global_config()
2221
BaseConfig.set_global(test_config)
2322

2423

2524
@then("retrieving global configuration should return the same instance")
2625
def step_then_check_global_config(context):
2726
scenario_context = get_current_scenario_context(context)
28-
test_config = scenario_context.get("test_config")
27+
test_config = BaseConfig.global_config()
2928
assert BaseConfig.global_config() is test_config
3029

3130

@@ -78,14 +77,12 @@ def step_when_initialize_base_config(context):
7877
scenario_context = get_current_scenario_context(context)
7978
config = TestConfig()
8079
BaseConfig.set_global(config)
81-
test_config = BaseConfig.global_config()
82-
scenario_context.store("test_config", test_config)
8380

8481

8582
@then('the ENVIRONMENT should be "{expected_value}"')
8683
def step_then_check_environment_variable(context, expected_value):
8784
scenario_context = get_current_scenario_context(context)
88-
test_config = scenario_context.get("test_config")
85+
test_config = BaseConfig.global_config()
8986
assert (
9087
test_config.ENVIRONMENT.name == expected_value
9188
), f"Expected '{expected_value}', but got '{test_config.ENVIRONMENT.name}'"

features/steps/datetime_utils_steps.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from behave import given, then, when
55
from behave.runner import Context
66

7+
from archipy.configs.base_config import BaseConfig
78
from archipy.helpers.utils.datetime_utils import DatetimeUtils
89
from features.test_helpers import get_current_scenario_context
910

@@ -276,7 +277,7 @@ def step_then_cached_with_historical_ttl(context: Context) -> None:
276277

277278
scenario_context = get_current_scenario_context(context)
278279
cache_entry = scenario_context.get("cache_entry")
279-
test_config = scenario_context.get("test_config")
280+
test_config = BaseConfig.global_config()
280281

281282
assert cache_entry is not None, "Cache entry should exist"
282283
assert test_config is not None, "Test config should be available"
@@ -298,7 +299,7 @@ def step_then_cached_with_standard_ttl(context: Context) -> None:
298299

299300
scenario_context = get_current_scenario_context(context)
300301
cache_entry = scenario_context.get("cache_entry")
301-
test_config = scenario_context.get("test_config")
302+
test_config = BaseConfig.global_config()
302303

303304
assert cache_entry is not None, "Cache entry should exist"
304305
assert test_config is not None, "Test config should be available"

features/steps/elastic_adapter_steps.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from features.test_helpers import get_current_scenario_context
1313
from archipy.adapters.elasticsearch.adapters import ElasticsearchAdapter, AsyncElasticsearchAdapter
14+
from archipy.configs.base_config import BaseConfig
1415

1516
logger = logging.getLogger(__name__)
1617

@@ -22,11 +23,11 @@ def get_es_adapter(context):
2223

2324
if is_async:
2425
if not hasattr(scenario_context, "async_adapter") or scenario_context.async_adapter is None:
25-
test_config = scenario_context.get("test_config")
26+
test_config = BaseConfig.global_config()
2627
scenario_context.async_adapter = AsyncElasticsearchAdapter(test_config.ELASTIC)
2728
return scenario_context.async_adapter
2829
if not hasattr(scenario_context, "adapter") or scenario_context.adapter is None:
29-
test_config = scenario_context.get("test_config")
30+
test_config = BaseConfig.global_config()
3031
scenario_context.adapter = ElasticsearchAdapter(test_config.ELASTIC)
3132
return scenario_context.adapter
3233

features/steps/jwt_utils_steps.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from behave import given, then, when
55

6+
from archipy.configs.base_config import BaseConfig
67
from archipy.helpers.utils.jwt_utils import JWTUtils
78
from archipy.models.errors import InvalidTokenError, TokenExpiredError
89
from features.test_helpers import get_current_scenario_context
@@ -18,7 +19,7 @@ def step_given_valid_user_uuid(context):
1819
def step_when_access_token_created(context):
1920
scenario_context = get_current_scenario_context(context)
2021
user_uuid = scenario_context.get("user_uuid")
21-
test_config = scenario_context.get("test_config")
22+
test_config = BaseConfig.global_config()
2223

2324
token = JWTUtils.create_access_token(user_uuid, auth_config=test_config.AUTH)
2425
scenario_context.store("token", token)
@@ -28,7 +29,7 @@ def step_when_access_token_created(context):
2829
def step_when_refresh_token_created(context):
2930
scenario_context = get_current_scenario_context(context)
3031
user_uuid = scenario_context.get("user_uuid")
31-
test_config = scenario_context.get("test_config")
32+
test_config = BaseConfig.global_config()
3233

3334
token = JWTUtils.create_refresh_token(user_uuid, auth_config=test_config.AUTH)
3435
scenario_context.store("token", token)
@@ -47,7 +48,7 @@ def step_then_jwt_token_returned(context):
4748
def step_given_valid_access_token(context):
4849
scenario_context = get_current_scenario_context(context)
4950
user_uuid = scenario_context.get("user_uuid")
50-
test_config = scenario_context.get("test_config")
51+
test_config = BaseConfig.global_config()
5152

5253
token = JWTUtils.create_access_token(user_uuid, auth_config=test_config.AUTH)
5354
scenario_context.store("token", token)
@@ -57,7 +58,7 @@ def step_given_valid_access_token(context):
5758
def step_given_valid_refresh_token(context):
5859
scenario_context = get_current_scenario_context(context)
5960
user_uuid = scenario_context.get("user_uuid")
60-
test_config = scenario_context.get("test_config")
61+
test_config = BaseConfig.global_config()
6162

6263
token = JWTUtils.create_refresh_token(user_uuid, auth_config=test_config.AUTH)
6364
scenario_context.store("token", token)
@@ -67,7 +68,7 @@ def step_given_valid_refresh_token(context):
6768
def step_given_expired_access_token(context):
6869
scenario_context = get_current_scenario_context(context)
6970
user_uuid = scenario_context.get("user_uuid")
70-
test_config = scenario_context.get("test_config")
71+
test_config = BaseConfig.global_config()
7172

7273
token = JWTUtils.create_access_token(
7374
user_uuid,
@@ -87,7 +88,7 @@ def step_given_invalid_token(context):
8788
def step_when_token_decoded(context):
8889
scenario_context = get_current_scenario_context(context)
8990
token = scenario_context.get("token")
90-
test_config = scenario_context.get("test_config")
91+
test_config = BaseConfig.global_config()
9192

9293
try:
9394
decoded_payload = JWTUtils.decode_token(token, auth_config=test_config.AUTH)

features/steps/keycloak_adapter_steps.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from features.test_helpers import get_current_scenario_context
77

88
from archipy.adapters.keycloak.adapters import AsyncKeycloakAdapter, KeycloakAdapter
9+
from archipy.configs.base_config import BaseConfig
910

1011

1112
def get_keycloak_adapter(context: Context) -> AsyncKeycloakAdapter | KeycloakAdapter:
@@ -15,11 +16,11 @@ def get_keycloak_adapter(context: Context) -> AsyncKeycloakAdapter | KeycloakAda
1516

1617
if is_async:
1718
if not hasattr(scenario_context, "async_adapter") or scenario_context.async_adapter is None:
18-
test_config = scenario_context.get("test_config")
19+
test_config = BaseConfig.global_config()
1920
scenario_context.async_adapter = AsyncKeycloakAdapter(test_config.KEYCLOAK)
2021
return scenario_context.async_adapter
2122
if not hasattr(scenario_context, "adapter") or scenario_context.adapter is None:
22-
test_config = scenario_context.get("test_config")
23+
test_config = BaseConfig.global_config()
2324
scenario_context.adapter = KeycloakAdapter(test_config.KEYCLOAK)
2425
return scenario_context.adapter
2526

features/steps/minio_adapter_steps.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from features.test_helpers import get_current_scenario_context
99

1010
from archipy.adapters.minio.adapters import MinioAdapter
11+
from archipy.configs.base_config import BaseConfig
1112

1213

1314
def get_minio_adapter(context):
1415
"""Get or initialize the MinIO adapter."""
1516
scenario_context = get_current_scenario_context(context)
1617
if not hasattr(scenario_context, "adapter") or scenario_context.adapter is None:
17-
test_config = scenario_context.get("test_config")
18+
test_config = BaseConfig.global_config()
1819
context.logger.info(f"Initializing MinIO adapter with endpoint: {test_config.MINIO.ENDPOINT}")
1920
scenario_context.adapter = MinioAdapter(test_config.MINIO)
2021
return scenario_context.adapter

0 commit comments

Comments
 (0)