@@ -402,6 +402,83 @@ def test_k8s_single_enable_disable():
402402 print ("OK" )
403403
404404
405+ def test_k8s_single_update ():
406+ """Update a single-role K8s ExApp to a new version."""
407+ print (" test_k8s_single_update..." , end = " " , flush = True )
408+
409+ # Get current version from app list
410+ list_output = occ_output ("app_api:app:list" )
411+ assert "app-skeleton-python" in list_output , f"App not in list before update: { list_output } "
412+
413+ # Build update JSON with a higher version (same image, bumped version)
414+ update_json = json .dumps ({
415+ "id" : "app-skeleton-python" ,
416+ "name" : "App Skeleton Python" ,
417+ "version" : "99.0.0" ,
418+ "docker-install" : {
419+ "registry" : "ghcr.io" ,
420+ "image" : "nextcloud/app-skeleton-python" ,
421+ "image-tag" : "latest" ,
422+ },
423+ })
424+
425+ r = run (
426+ [
427+ "php" , "occ" , "--no-warnings" , "app_api:app:update" ,
428+ "app-skeleton-python" ,
429+ "--json-info" , update_json ,
430+ "--wait-finish" ,
431+ ],
432+ stdout = PIPE , stderr = PIPE , timeout = 600 ,
433+ )
434+ assert r .returncode == 0 , f"Update failed (exit { r .returncode } ): { r .stdout .decode ()} "
435+
436+ # Verify version changed
437+ list_output = occ_output ("app_api:app:list" )
438+ assert "99.0.0" in list_output , f"Expected version 99.0.0 in app list: { list_output } "
439+
440+ # Verify K8s resources still exist
441+ deploy_output = kubectl_output ("get deploy -o name" )
442+ assert "app-skeleton-python" in deploy_output , f"No deployment after update: { deploy_output } "
443+
444+ if not IS_MANUAL :
445+ svc_output = kubectl_output ("get svc -o name" )
446+ assert "app-skeleton-python" in svc_output , f"No service after update: { svc_output } "
447+
448+ # Verify app is enabled after update
449+ assert "enabled" in list_output , f"App not enabled after update: { list_output } "
450+ print ("OK" )
451+
452+
453+ def test_k8s_single_update_same_version ():
454+ """Update with the same version should be a no-op."""
455+ print (" test_k8s_single_update_same_version..." , end = " " , flush = True )
456+
457+ same_json = json .dumps ({
458+ "id" : "app-skeleton-python" ,
459+ "name" : "App Skeleton Python" ,
460+ "version" : "99.0.0" ,
461+ "docker-install" : {
462+ "registry" : "ghcr.io" ,
463+ "image" : "nextcloud/app-skeleton-python" ,
464+ "image-tag" : "latest" ,
465+ },
466+ })
467+
468+ r = run (
469+ [
470+ "php" , "occ" , "--no-warnings" , "app_api:app:update" ,
471+ "app-skeleton-python" ,
472+ "--json-info" , same_json ,
473+ ],
474+ stdout = PIPE , stderr = PIPE , timeout = 60 ,
475+ )
476+ assert r .returncode == 0 , f"Same-version update failed: { r .stdout .decode ()} "
477+ output = r .stdout .decode ()
478+ assert "already updated" in output , f"Expected 'already updated' message, got: { output } "
479+ print ("OK" )
480+
481+
405482def test_k8s_single_unregister_keep_data ():
406483 """Unregister K8s ExApp — default keeps PVC."""
407484 print (" test_k8s_single_unregister_keep_data..." , end = " " , flush = True )
@@ -474,6 +551,8 @@ def run_single_role_tests():
474551 print ("\n === Group B: K8s Single-Role Deploy Lifecycle ===" )
475552 test_k8s_single_deploy ()
476553 test_k8s_single_enable_disable ()
554+ test_k8s_single_update ()
555+ test_k8s_single_update_same_version ()
477556 test_k8s_single_unregister_keep_data ()
478557 test_k8s_single_deploy_rm_data ()
479558 print ("=== Group B: All single-role tests passed ===\n " )
@@ -587,6 +666,50 @@ def test_k8s_multi_enable_disable():
587666 print ("OK" )
588667
589668
669+ def test_k8s_multi_update ():
670+ """Update a multi-role K8s ExApp to a new version."""
671+ print (" test_k8s_multi_update..." , end = " " , flush = True )
672+
673+ update_json = json .dumps ({
674+ "id" : "app-skeleton-python" ,
675+ "name" : "App Skeleton Python" ,
676+ "version" : "2.0.0" ,
677+ "docker-install" : {
678+ "registry" : "ghcr.io" ,
679+ "image" : "nextcloud/app-skeleton-python" ,
680+ "image-tag" : "latest" ,
681+ },
682+ "k8s-service-roles" : [
683+ {"name" : "api" , "env" : "SERVICE_ROLE=api" , "expose" : True },
684+ {"name" : "worker" , "env" : "SERVICE_ROLE=worker" , "expose" : False },
685+ ],
686+ })
687+
688+ r = run (
689+ [
690+ "php" , "occ" , "--no-warnings" , "app_api:app:update" ,
691+ "app-skeleton-python" ,
692+ "--json-info" , update_json ,
693+ "--wait-finish" ,
694+ ],
695+ stdout = PIPE , stderr = PIPE , timeout = 600 ,
696+ )
697+ assert r .returncode == 0 , f"Multi-role update failed (exit { r .returncode } ): { r .stdout .decode ()} "
698+
699+ # Verify version changed
700+ list_output = occ_output ("app_api:app:list" )
701+ assert "2.0.0" in list_output , f"Expected version 2.0.0 in app list: { list_output } "
702+
703+ # Verify both deployments still exist
704+ deploy_output = kubectl_output ("get deploy -o name" )
705+ deploy_names = [line for line in deploy_output .strip ().split ("\n " ) if "app-skeleton-python" in line ]
706+ assert len (deploy_names ) >= 2 , f"Expected 2 deployments after update, got { len (deploy_names )} : { deploy_output } "
707+
708+ # Verify app is enabled after update
709+ assert "enabled" in list_output , f"App not enabled after update: { list_output } "
710+ print ("OK" )
711+
712+
590713def test_k8s_multi_unregister ():
591714 """Unregister multi-role ExApp — both deployments and service removed."""
592715 print (" test_k8s_multi_unregister..." , end = " " , flush = True )
@@ -612,6 +735,7 @@ def run_multi_role_tests():
612735 print ("\n === Group C: K8s Multi-Role Deploy Lifecycle ===" )
613736 test_k8s_multi_deploy ()
614737 test_k8s_multi_enable_disable ()
738+ test_k8s_multi_update ()
615739 test_k8s_multi_unregister ()
616740 print ("=== Group C: All multi-role tests passed ===\n " )
617741
0 commit comments