1- """Integration tests for Unleash provider using a running Unleash instance."""
1+ """Integration tests for Unleash provider using testcontainers."""
2+
3+ from datetime import datetime , timezone
4+ import time
25
36from openfeature import api
47from openfeature .contrib .provider .unleash import UnleashProvider
58from openfeature .evaluation_context import EvaluationContext
9+ import psycopg2
610import pytest
711import requests
12+ from testcontainers .core .container import DockerContainer
13+ from testcontainers .postgres import PostgresContainer
814
9-
10- UNLEASH_URL = "http://0.0.0.0:4242/api"
15+ # Configuration for the running Unleash instance (will be set by fixtures)
16+ UNLEASH_URL = None
1117API_TOKEN = "default:development.unleash-insecure-api-token"
1218ADMIN_TOKEN = "user:76672ac99726f8e48a1bbba16b7094a50d1eee3583d1e8457e12187a"
1319
1420
21+ class UnleashContainer (DockerContainer ):
22+ """Custom Unleash container with health check."""
23+
24+ def __init__ (self , postgres_url : str , ** kwargs ):
25+ super ().__init__ ("unleashorg/unleash-server:latest" , ** kwargs )
26+ self .postgres_url = postgres_url
27+
28+ def _configure (self ):
29+ self .with_env ("DATABASE_URL" , self .postgres_url )
30+ self .with_env ("DATABASE_URL_FILE" , "" )
31+ self .with_env ("DATABASE_SSL" , "false" )
32+ self .with_env ("DATABASE_SSL_REJECT_UNAUTHORIZED" , "false" )
33+ self .with_env ("LOG_LEVEL" , "info" )
34+ self .with_env ("PORT" , "4242" )
35+ self .with_env ("HOST" , "0.0.0.0" )
36+ self .with_env ("ADMIN_AUTHENTICATION" , "none" )
37+ self .with_env ("AUTH_ENABLE" , "false" )
38+ self .with_env ("INIT_CLIENT_API_TOKENS" , API_TOKEN )
39+ # Expose the Unleash port
40+ self .with_exposed_ports (4242 )
41+
42+
43+ def insert_admin_token (postgres_container ):
44+ """Insert admin token into the Unleash database."""
45+ url = postgres_container .get_connection_url ()
46+ conn = psycopg2 .connect (url )
47+
48+ try :
49+ with conn .cursor () as cursor :
50+ cursor .execute (
51+ """
52+ INSERT INTO "public"."personal_access_tokens"
53+ ("secret", "description", "user_id", "expires_at", "seen_at", "created_at", "id")
54+ VALUES (%s, %s, %s, %s, %s, %s, %s)
55+ ON CONFLICT (id) DO NOTHING
56+ """ ,
57+ (
58+ "user:76672ac99726f8e48a1bbba16b7094a50d1eee3583d1e8457e12187a" ,
59+ "my-token" ,
60+ 1 ,
61+ datetime (3025 , 1 , 1 , 1 , 0 , 0 , 0 , tzinfo = timezone .utc ),
62+ datetime .now (timezone .utc ),
63+ datetime .now (timezone .utc ),
64+ 1 ,
65+ ),
66+ )
67+ conn .commit ()
68+ print ("Admin token inserted successfully" )
69+ except Exception as e :
70+ print (f"Error inserting admin token: { e } " )
71+ conn .rollback ()
72+ finally :
73+ conn .close ()
74+
75+
1576def create_test_flags ():
1677 """Create test flags in the Unleash instance."""
1778 flags = [
@@ -58,7 +119,7 @@ def create_test_flags():
58119 for flag in flags :
59120 try :
60121 response = requests .post (
61- f"{ UNLEASH_URL } /admin/projects/default/features" ,
122+ f"{ UNLEASH_URL } /api/ admin/projects/default/features" ,
62123 headers = headers ,
63124 json = flag ,
64125 timeout = 10 ,
@@ -157,7 +218,7 @@ def add_strategy_with_variants(flag_name: str, headers: dict):
157218 }
158219
159220 strategy_response = requests .post (
160- f"{ UNLEASH_URL } /admin/projects/default/features/{ flag_name } /environments/development/strategies" ,
221+ f"{ UNLEASH_URL } /api/ admin/projects/default/features/{ flag_name } /environments/development/strategies" ,
161222 headers = headers ,
162223 json = strategy_payload ,
163224 timeout = 10 ,
@@ -174,7 +235,7 @@ def enable_flag(flag_name: str, headers: dict):
174235 """Enable a flag in the development environment."""
175236 try :
176237 enable_response = requests .post (
177- f"{ UNLEASH_URL } /admin/projects/default/features/{ flag_name } /environments/development/on" ,
238+ f"{ UNLEASH_URL } /api/ admin/projects/default/features/{ flag_name } /environments/development/on" ,
178239 headers = headers ,
179240 timeout = 10 ,
180241 )
@@ -186,19 +247,95 @@ def enable_flag(flag_name: str, headers: dict):
186247 print (f"Error enabling flag '{ flag_name } ': { e } " )
187248
188249
250+ @pytest .fixture (scope = "session" )
251+ def postgres_container ():
252+ """Create and start PostgreSQL container."""
253+ with PostgresContainer ("postgres:15" , driver = None ) as postgres :
254+ postgres .start ()
255+ postgres_url = postgres .get_connection_url ()
256+ print (f"PostgreSQL started at: { postgres_url } " )
257+
258+ yield postgres
259+
260+
261+ @pytest .fixture (scope = "session" )
262+ def unleash_container (postgres_container ):
263+ """Create and start Unleash container with PostgreSQL dependency."""
264+ global UNLEASH_URL
265+
266+ postgres_url = postgres_container .get_connection_url ()
267+ postgres_bridge_ip = postgres_container .get_docker_client ().bridge_ip (
268+ postgres_container ._container .id
269+ )
270+
271+ # Create internal URL using the bridge IP and internal port (5432)
272+ exposed_port = postgres_container .get_exposed_port (5432 )
273+ internal_url = postgres_url .replace ("localhost" , postgres_bridge_ip ).replace (
274+ f":{ exposed_port } " , ":5432"
275+ )
276+
277+ unleash = UnleashContainer (internal_url )
278+
279+ with unleash as container :
280+ print ("Starting Unleash container..." )
281+ container .start ()
282+ print ("Unleash container started" )
283+
284+ # Wait for health check to pass
285+ print ("Waiting for Unleash container to be healthy..." )
286+ max_wait_time = 60 # 1 minute max wait
287+ start_time = time .time ()
288+
289+ while time .time () - start_time < max_wait_time :
290+ try :
291+ # Get the exposed port
292+ try :
293+ exposed_port = container .get_exposed_port (4242 )
294+ unleash_url = f"http://localhost:{ exposed_port } "
295+ print (f"Trying health check at: { unleash_url } " )
296+ except Exception as port_error :
297+ print (f"Port not ready yet: { port_error } " )
298+ time .sleep (2 )
299+ continue
300+
301+ # Try to connect to health endpoint
302+ response = requests .get (f"{ unleash_url } /health" , timeout = 5 )
303+ if response .status_code == 200 :
304+ print ("Unleash container is healthy!" )
305+ break
306+
307+ print (f"Health check failed, status: { response .status_code } " )
308+ time .sleep (2 )
309+
310+ except Exception as e :
311+ print (f"Health check error: { e } " )
312+ time .sleep (2 )
313+ else :
314+ raise Exception ("Unleash container did not become healthy within timeout" )
315+
316+ # Get the exposed port and set global URL
317+ UNLEASH_URL = f"http://localhost:{ container .get_exposed_port (4242 )} "
318+ print (f"Unleash started at: { unleash_url } " )
319+
320+ insert_admin_token (postgres_container )
321+ print ("Admin token inserted into database" )
322+
323+ yield container , unleash_url
324+
325+
189326@pytest .fixture (scope = "session" , autouse = True )
190- def setup_test_flags ():
327+ def setup_test_flags (unleash_container ):
191328 """Setup test flags before running any tests."""
192329 print ("Creating test flags in Unleash..." )
193330 create_test_flags ()
194331 print ("Test flags setup completed" )
195332
196333
197- @pytest .fixture
198- def unleash_provider ():
334+ @pytest .fixture ( scope = "session" )
335+ def unleash_provider (setup_test_flags ):
199336 """Create an Unleash provider instance for testing."""
200337 provider = UnleashProvider (
201- url = UNLEASH_URL ,
338+ url = f" { UNLEASH_URL } /api" ,
202339 app_name = "test-app" ,
203340 api_token = API_TOKEN ,
204341 )
@@ -208,7 +345,7 @@ def unleash_provider():
208345 provider .shutdown ()
209346
210347
211- @pytest .fixture
348+ @pytest .fixture ( scope = "session" )
212349def client (unleash_provider ):
213350 """Create an OpenFeature client with the Unleash provider."""
214351 # Set the provider globally
@@ -219,24 +356,10 @@ def client(unleash_provider):
219356@pytest .mark .integration
220357def test_integration_health_check ():
221358 """Test that Unleash health check endpoint is accessible."""
222- response = requests .get (f"{ UNLEASH_URL . replace ( '/api' , '' ) } /health" , timeout = 5 )
359+ response = requests .get (f"{ UNLEASH_URL } /health" , timeout = 5 )
223360 assert response .status_code == 200
224361
225362
226- @pytest .mark .integration
227- def test_integration_provider_initialization (unleash_provider ):
228- """Test that the Unleash provider can be initialized."""
229- assert unleash_provider is not None
230- assert unleash_provider .client is not None
231-
232-
233- @pytest .mark .integration
234- def test_integration_provider_metadata (unleash_provider ):
235- """Test that the provider returns correct metadata."""
236- metadata = unleash_provider .get_metadata ()
237- assert metadata .name == "Unleash Provider"
238-
239-
240363@pytest .mark .integration
241364def test_integration_flag_details_resolution (unleash_provider ):
242365 """Test flag details resolution with the Unleash provider."""
@@ -250,13 +373,6 @@ def test_integration_flag_details_resolution(unleash_provider):
250373 assert details .value is True
251374
252375
253- @pytest .mark .integration
254- def test_integration_provider_status (unleash_provider ):
255- """Test that the provider status is correctly reported."""
256- status = unleash_provider .get_status ()
257- assert status .value == "READY"
258-
259-
260376@pytest .mark .integration
261377def test_integration_boolean_flag_resolution (unleash_provider ):
262378 """Test boolean flag resolution with the Unleash provider."""
0 commit comments