@@ -185,6 +185,7 @@ async def test_save_profile_with_builder(
185185 self ,
186186 sdk_client : CodesphereSDK ,
187187 test_workspace : Workspace ,
188+ test_plan_id : int ,
188189 ):
189190 """save_profile should create a profile file using ProfileBuilder."""
190191 workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
@@ -196,6 +197,7 @@ async def test_save_profile_with_builder(
196197 .add_step ("echo 'Installing dependencies'" )
197198 .done ()
198199 .add_reactive_service ("web" )
200+ .plan (test_plan_id )
199201 .add_step ("echo 'Starting server'" )
200202 .add_port (3000 , public = True )
201203 .replicas (1 )
@@ -280,6 +282,7 @@ async def test_get_profile_returns_yaml_content(
280282 self ,
281283 sdk_client : CodesphereSDK ,
282284 test_workspace : Workspace ,
285+ test_plan_id : int ,
283286 ):
284287 """get_profile should return the YAML content of a saved profile."""
285288 workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
@@ -291,6 +294,7 @@ async def test_get_profile_returns_yaml_content(
291294 .add_step ("npm install" )
292295 .done ()
293296 .add_reactive_service ("api" )
297+ .plan (test_plan_id )
294298 .add_step ("npm start" )
295299 .add_port (8080 )
296300 .env ("NODE_ENV" , "production" )
@@ -357,6 +361,7 @@ async def test_complex_profile_roundtrip(
357361 self ,
358362 sdk_client : CodesphereSDK ,
359363 test_workspace : Workspace ,
364+ test_plan_id : int ,
360365 ):
361366 """A complex profile should survive save and retrieve."""
362367 workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
@@ -372,6 +377,7 @@ async def test_complex_profile_roundtrip(
372377 .add_step ("npm test" )
373378 .done ()
374379 .add_reactive_service ("frontend" )
380+ .plan (test_plan_id )
375381 .add_step ("npm run serve" )
376382 .add_port (3000 , public = True )
377383 .add_path ("/" , port = 3000 )
@@ -380,6 +386,7 @@ async def test_complex_profile_roundtrip(
380386 .health_endpoint ("/health" )
381387 .done ()
382388 .add_reactive_service ("backend" )
389+ .plan (test_plan_id )
383390 .add_step ("python -m uvicorn main:app" )
384391 .add_port (8000 )
385392 .add_path ("/api" , port = 8000 , strip_path = True )
@@ -412,6 +419,7 @@ async def test_profile_with_special_characters_in_env(
412419 self ,
413420 sdk_client : CodesphereSDK ,
414421 test_workspace : Workspace ,
422+ test_plan_id : int ,
415423 ):
416424 """Profile with special characters in env values should work."""
417425 workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
@@ -420,6 +428,7 @@ async def test_profile_with_special_characters_in_env(
420428 profile = (
421429 ProfileBuilder ()
422430 .add_reactive_service ("app" )
431+ .plan (test_plan_id )
423432 .add_step ("npm start" )
424433 .add_port (3000 )
425434 .env ("DATABASE_URL" , "postgres://user:p@ss=word@localhost:5432/db" )
@@ -437,3 +446,148 @@ async def test_profile_with_special_characters_in_env(
437446
438447 finally :
439448 await workspace .landscape .delete_profile (profile_name )
449+
450+
451+ class TestLandscapeDeploymentWorkflow :
452+ """Integration tests for the complete landscape deployment workflow."""
453+
454+ async def test_full_landscape_workflow_deploy_teardown_delete (
455+ self ,
456+ sdk_client : CodesphereSDK ,
457+ test_workspace : Workspace ,
458+ test_plan_id : int ,
459+ ):
460+ """Test the complete workflow: create profile, deploy, teardown, delete profile."""
461+ workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
462+ profile_name = "sdk-workflow-test"
463+
464+ # Step 1: Create a valid profile with ProfileBuilder
465+ profile = (
466+ ProfileBuilder ()
467+ .prepare ()
468+ .add_step ("echo 'Preparing...'" , name = "Prepare" )
469+ .done ()
470+ .add_reactive_service ("web" )
471+ .plan (test_plan_id )
472+ .add_step ("echo 'Starting web service' && sleep infinity" )
473+ .add_port (3000 , public = True )
474+ .add_path ("/" , port = 3000 )
475+ .replicas (1 )
476+ .env ("NODE_ENV" , "production" )
477+ .done ()
478+ .build ()
479+ )
480+
481+ try :
482+ # Step 2: Save the profile
483+ await workspace .landscape .save_profile (profile_name , profile )
484+
485+ # Verify profile exists
486+ profiles = await workspace .landscape .list_profiles ()
487+ profile_names = [p .name for p in profiles ]
488+ assert profile_name in profile_names , "Profile should exist after saving"
489+
490+ # Verify profile content
491+ content = await workspace .landscape .get_profile (profile_name )
492+ assert "schemaVersion: v0.2" in content
493+ assert "web:" in content
494+ assert f"plan: { test_plan_id } " in content
495+
496+ # Step 3: Deploy the landscape
497+ await workspace .landscape .deploy (profile = profile_name )
498+
499+ # Step 4: Teardown the landscape
500+ await workspace .landscape .teardown ()
501+
502+ finally :
503+ # Step 5: Delete the profile
504+ await workspace .landscape .delete_profile (profile_name )
505+
506+ # Verify profile is deleted
507+ profiles = await workspace .landscape .list_profiles ()
508+ profile_names = [p .name for p in profiles ]
509+ assert profile_name not in profile_names , (
510+ "Profile should not exist after deletion"
511+ )
512+
513+ async def test_deploy_and_teardown_only (
514+ self ,
515+ sdk_client : CodesphereSDK ,
516+ test_workspace : Workspace ,
517+ test_plan_id : int ,
518+ ):
519+ """Test deploy and teardown without profile deletion."""
520+ workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
521+ profile_name = "sdk-deploy-teardown-test"
522+
523+ profile = (
524+ ProfileBuilder ()
525+ .prepare ()
526+ .add_step ("echo 'Setup complete'" )
527+ .done ()
528+ .add_reactive_service ("api" )
529+ .plan (test_plan_id )
530+ .add_step ("echo 'API running' && sleep infinity" )
531+ .add_port (8080 )
532+ .add_path ("/api" , port = 8080 )
533+ .replicas (1 )
534+ .done ()
535+ .build ()
536+ )
537+
538+ try :
539+ # Save profile
540+ await workspace .landscape .save_profile (profile_name , profile )
541+
542+ # Deploy
543+ await workspace .landscape .deploy (profile = profile_name )
544+
545+ # Teardown
546+ await workspace .landscape .teardown ()
547+
548+ # Profile should still exist after teardown
549+ profiles = await workspace .landscape .list_profiles ()
550+ profile_names = [p .name for p in profiles ]
551+ assert profile_name in profile_names , (
552+ "Profile should still exist after teardown"
553+ )
554+
555+ finally :
556+ # Cleanup
557+ await workspace .landscape .delete_profile (profile_name )
558+
559+ async def test_profile_deletion_removes_from_list (
560+ self ,
561+ sdk_client : CodesphereSDK ,
562+ test_workspace : Workspace ,
563+ test_plan_id : int ,
564+ ):
565+ """Verify that deleting a profile removes it from the profile list."""
566+ workspace = await sdk_client .workspaces .get (workspace_id = test_workspace .id )
567+ profile_name = "sdk-deletion-verify-test"
568+
569+ profile = (
570+ ProfileBuilder ()
571+ .add_reactive_service ("service" )
572+ .plan (test_plan_id )
573+ .add_step ("echo 'running'" )
574+ .add_port (3000 )
575+ .done ()
576+ .build ()
577+ )
578+
579+ # Create profile
580+ await workspace .landscape .save_profile (profile_name , profile )
581+
582+ # Verify it exists
583+ profiles_before = await workspace .landscape .list_profiles ()
584+ assert profile_name in [p .name for p in profiles_before ]
585+
586+ # Delete profile
587+ await workspace .landscape .delete_profile (profile_name )
588+
589+ # Verify it's gone
590+ profiles_after = await workspace .landscape .list_profiles ()
591+ assert profile_name not in [p .name for p in profiles_after ], (
592+ f"Profile '{ profile_name } ' should be removed after deletion"
593+ )
0 commit comments