Skip to content

Commit 5faae38

Browse files
committed
cocalc-api: adjust org tests after changes to organization logic in server
1 parent d24ec9a commit 5faae38

File tree

5 files changed

+186
-110
lines changed

5 files changed

+186
-110
lines changed

src/python/cocalc-api/src/cocalc_api/hub.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class Hub:
1010
def __init__(self, api_key: str, host: str = "https://cocalc.com"):
1111
self.api_key = api_key
1212
self.host = host
13-
self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"})
13+
# Use longer timeout for API calls (30 seconds instead of default 5)
14+
self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=30.0)
1415

1516
def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any:
1617
"""

src/python/cocalc-api/src/cocalc_api/project.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ def __init__(self, api_key: str, host: str = "https://cocalc.com", project_id: O
1010
self.project_id = project_id
1111
self.api_key = api_key
1212
self.host = host
13-
self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"})
13+
# Use longer timeout for API calls (30 seconds instead of default 5)
14+
self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=30.0)
1415

1516
def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any:
1617
"""

src/python/cocalc-api/tests/conftest.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,10 @@ def hub(api_key, cocalc_host):
4747

4848

4949
@pytest.fixture(scope="session")
50-
def temporary_project(hub):
50+
def temporary_project(hub, request):
5151
"""
5252
Create a temporary project for testing and return project info.
53-
54-
Note: Since there's no project deletion API available, the project
55-
will remain after tests. It can be manually deleted if needed.
53+
Uses a session-scoped fixture so only ONE project is created for the entire test suite.
5654
"""
5755
import time
5856

@@ -61,15 +59,20 @@ def temporary_project(hub):
6159
title = f"CoCalc API Test {timestamp}"
6260
description = "Temporary project created by cocalc-api tests"
6361

62+
print("\n" + "="*70)
63+
print("=== Creating temporary project for entire test session ===")
64+
print("=== THIS SHOULD ONLY PRINT ONCE ===")
65+
print("="*70)
6466
project_id = hub.projects.create_project(title=title, description=description)
67+
print(f"Created project {project_id}")
68+
print("="*70)
6569

6670
# Start the project so it can respond to API calls
6771
try:
6872
hub.projects.start(project_id)
69-
print(f"Started project {project_id}, waiting for it to become ready...")
73+
print(f"Starting project {project_id}, waiting for it to become ready...")
7074

7175
# Wait for project to be ready (can take 10-15 seconds)
72-
import time
7376
from cocalc_api import Project
7477

7578
for attempt in range(10):
@@ -82,34 +85,39 @@ def temporary_project(hub):
8285
break
8386
except Exception:
8487
if attempt == 9: # Last attempt
85-
print(f"Warning: Project {project_id} did not become ready within 50 seconds")
88+
print(f"Warning: Project {project_id} did not become ready within 50 seconds")
8689

8790
except Exception as e:
88-
print(f"Warning: Failed to start project {project_id}: {e}")
91+
print(f"Warning: Failed to start project {project_id}: {e}")
8992

9093
project_info = {'project_id': project_id, 'title': title, 'description': description}
9194

92-
yield project_info
93-
94-
# Cleanup: Stop the project and attempt to delete it
95-
print(f"\nCleaning up test project '{title}' (ID: {project_id})...")
96-
97-
try:
98-
# Stop the project first
99-
print(f" Stopping project {project_id}...")
100-
hub.projects.stop(project_id)
101-
print(f" Project {project_id} stopped successfully")
102-
except Exception as e:
103-
print(f" Failed to stop project {project_id}: {e}")
104-
105-
try:
106-
# Delete the project using the new delete method
107-
print(f" Deleting project {project_id}...")
108-
hub.projects.delete(project_id)
109-
print(f" Project {project_id} deleted successfully")
110-
except Exception as e:
111-
print(f" Failed to delete project {project_id}: {e}")
112-
print(" Project is stopped but may still exist - manual cleanup recommended")
95+
# Register cleanup using finalizer (more reliable than yield teardown)
96+
def cleanup():
97+
print(f"\n=== Cleaning up test project '{title}' (ID: {project_id}) ===")
98+
99+
try:
100+
# Stop the project first
101+
print(f"Stopping project {project_id}...")
102+
hub.projects.stop(project_id)
103+
print("✓ Project stop command sent")
104+
# Wait for the project process to actually terminate
105+
time.sleep(3)
106+
print(f"✓ Waited for project {project_id} to stop")
107+
except Exception as e:
108+
print(f"⚠ Failed to stop project {project_id}: {e}")
109+
110+
try:
111+
# Delete the project
112+
print(f"Deleting project {project_id}...")
113+
hub.projects.delete(project_id)
114+
print(f"✓ Project {project_id} deleted")
115+
except Exception as e:
116+
print(f"⚠ Failed to delete project {project_id}: {e}")
117+
118+
request.addfinalizer(cleanup)
119+
120+
return project_info
113121

114122

115123
@pytest.fixture(scope="session")

src/python/cocalc-api/tests/test_hub.py

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,26 @@ class TestHubSystem:
1212
"""Tests for Hub system operations."""
1313

1414
def test_ping(self, hub):
15-
"""Test basic ping connectivity."""
16-
result = hub.system.ping()
17-
assert result is not None
18-
# The ping response should contain some basic server info
19-
assert isinstance(result, dict)
15+
"""Test basic ping connectivity with retry logic."""
16+
# Retry with exponential backoff in case server is still starting up
17+
max_attempts = 5
18+
delay = 2 # Start with 2 second delay
19+
20+
for attempt in range(max_attempts):
21+
try:
22+
result = hub.system.ping()
23+
assert result is not None
24+
# The ping response should contain some basic server info
25+
assert isinstance(result, dict)
26+
print(f"✓ Server ping successful on attempt {attempt + 1}")
27+
return # Success!
28+
except Exception as e:
29+
if attempt < max_attempts - 1:
30+
print(f"Ping attempt {attempt + 1} failed, retrying in {delay}s... ({e})")
31+
time.sleep(delay)
32+
delay *= 2 # Exponential backoff
33+
else:
34+
pytest.fail(f"Server ping failed after {max_attempts} attempts: {e}")
2035

2136
def test_hub_initialization(self, api_key, cocalc_host):
2237
"""Test Hub client initialization."""
@@ -31,11 +46,12 @@ def test_invalid_api_key(self, cocalc_host):
3146
with pytest.raises((ValueError, RuntimeError, Exception)): # Should raise authentication error
3247
hub.system.ping()
3348

34-
def test_ping_timeout(self, api_key, cocalc_host):
35-
"""Test ping with timeout parameter."""
36-
hub = Hub(api_key=api_key, host=cocalc_host)
37-
result = hub.system.ping()
38-
assert result is not None
49+
def test_multiple_pings(self, hub):
50+
"""Test that multiple ping calls work consistently."""
51+
for _i in range(3):
52+
result = hub.system.ping()
53+
assert result is not None
54+
assert isinstance(result, dict)
3955

4056

4157
class TestHubProjects:
@@ -50,8 +66,22 @@ def test_create_project(self, hub):
5066

5167
project_id = hub.projects.create_project(title=title, description=description)
5268

53-
assert project_id is not None
54-
assert_valid_uuid(project_id, "Project ID")
69+
try:
70+
assert project_id is not None
71+
assert_valid_uuid(project_id, "Project ID")
72+
print(f"✓ Created project: {project_id}")
73+
finally:
74+
# Cleanup: stop then delete the project
75+
try:
76+
print(f"Cleaning up test project {project_id}...")
77+
hub.projects.stop(project_id)
78+
print("✓ Project stop command sent")
79+
time.sleep(3) # Wait for process to terminate
80+
print(f"✓ Waited for project {project_id} to stop")
81+
hub.projects.delete(project_id)
82+
print(f"✓ Project {project_id} deleted")
83+
except Exception as e:
84+
print(f"⚠ Failed to cleanup project {project_id}: {e}")
5585

5686
def test_list_projects(self, hub):
5787
"""Test listing projects."""
@@ -131,13 +161,19 @@ def test_project_lifecycle(self, hub):
131161
else:
132162
print("5. Skipping command execution - project not ready")
133163

134-
# 3. Delete the project
135-
print("6. Deleting project...")
164+
# 3. Stop and delete the project
165+
print("6. Stopping project...")
166+
hub.projects.stop(project_id)
167+
print(" ✓ Project stop command sent")
168+
time.sleep(3) # Wait for process to terminate
169+
print(" ✓ Waited for project to stop")
170+
171+
print("7. Deleting project...")
136172
delete_result = hub.projects.delete(project_id)
137-
print(f" Delete result: {delete_result}")
173+
print(f" Delete result: {delete_result}")
138174

139175
# 4. Verify project is marked as deleted in database
140-
print("7. Verifying project is marked as deleted...")
176+
print("8. Verifying project is marked as deleted...")
141177
projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id, all=True)
142178
assert len(projects) == 1, f"Expected 1 project (still in DB), found {len(projects)}"
143179
project = projects[0]
@@ -148,12 +184,16 @@ def test_project_lifecycle(self, hub):
148184
print("✅ Project lifecycle test completed successfully!")
149185

150186
except Exception as e:
151-
# Cleanup: attempt to delete project if test fails
187+
# Cleanup: attempt to stop and delete project if test fails
152188
print(f"\n❌ Test failed: {e}")
153189
try:
154-
print("Attempting cleanup...")
190+
print("Attempting cleanup: stopping then deleting project...")
191+
hub.projects.stop(project_id)
192+
print("✓ Project stop command sent")
193+
time.sleep(3) # Wait for process to terminate
194+
print("✓ Waited for project to stop")
155195
hub.projects.delete(project_id)
156-
print("✓ Cleanup successful")
196+
print("✓ Project deleted")
157197
except Exception as cleanup_error:
158198
print(f"❌ Cleanup failed: {cleanup_error}")
159199
raise e

0 commit comments

Comments
 (0)