Path: blob/master/src/python/cocalc-api/tests/test_project.py
5609 views
"""1Tests for Project client functionality.2"""3import os4import pytest56from cocalc_api import Project7from .conftest import assert_valid_uuid8910class TestProjectCreation:11"""Tests for project creation and management."""1213def test_create_temporary_project(self, temporary_project):14"""Test that a temporary project is created successfully."""15assert temporary_project is not None16assert 'project_id' in temporary_project17assert 'title' in temporary_project18assert 'description' in temporary_project19assert temporary_project['title'].startswith('CoCalc API Test ')20assert temporary_project['description'] == "Temporary project created by cocalc-api tests"21# Project ID should be a valid UUID22assert_valid_uuid(temporary_project['project_id'], "Project ID")2324def test_project_exists_in_list(self, hub, temporary_project):25"""Test that the created project appears in the projects list."""26projects = hub.projects.get(all=True)27project_ids = [p['project_id'] for p in projects]28assert temporary_project['project_id'] in project_ids293031class TestProjectSystem:32"""Tests for Project system operations."""3334def test_ping(self, project_client):35"""Test basic ping connectivity to project."""36result = project_client.system.ping()37assert result is not None38assert isinstance(result, dict)3940def test_project_initialization(self, api_key, cocalc_host):41"""Test Project client initialization."""42project_id = "test-project-id"43project = Project(project_id=project_id, api_key=api_key, host=cocalc_host)44assert project.project_id == project_id45assert project.api_key == api_key46assert project.host == cocalc_host47assert project.client is not None4849def test_project_with_temporary_project(self, project_client, temporary_project):50"""Test Project client using the temporary project."""51assert project_client.project_id == temporary_project['project_id']52# Test that we can ping the specific project53result = project_client.system.ping()54assert result is not None55assert isinstance(result, dict)5657def test_exec_command(self, project_client):58"""Test executing shell commands in the project."""59# Test running 'date -Is' to get ISO date with seconds60result = project_client.system.exec(command="date", args=["-Is"])6162# Check the result structure63assert 'stdout' in result64assert 'stderr' in result65assert 'exit_code' in result6667# Should succeed68assert result['exit_code'] == 06970# Should have minimal stderr71assert result['stderr'] == '' or len(result['stderr']) == 07273# Parse the returned date and compare with current time74from datetime import datetime75import re7677date_output = result['stdout'].strip()78# Expected format: 2025-09-29T12:34:56+00:00 or similar7980# Check if the output matches ISO format81iso_pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$'82assert re.match(iso_pattern, date_output), f"Date output '{date_output}' doesn't match ISO format"8384# Parse the date from the command output85# Remove the timezone for comparison (date -Is includes timezone)86date_part = date_output[:19] # Take YYYY-MM-DDTHH:MM:SS part87remote_time = datetime.fromisoformat(date_part)8889# Get current time90current_time = datetime.now()9192# Check if the times are close (within 60 seconds)93time_diff = abs((current_time - remote_time).total_seconds())94assert time_diff < 60, f"Time difference too large: {time_diff} seconds. Remote: {date_output}, Local: {current_time.isoformat()}"9596def test_exec_stderr_and_exit_code(self, project_client):97"""Test executing a command that writes to stderr and returns a specific exit code."""98# Use bash to echo to stderr and exit with code 4299bash_script = "echo 'test error message' >&2; exit 42"100101# The API raises an exception for non-zero exit codes102# but includes the stderr and exit code information in the error message103with pytest.raises(RuntimeError) as exc_info:104project_client.system.exec(command=bash_script, bash=True)105106error_message = str(exc_info.value)107108# Verify the error message contains expected information109assert "exited with nonzero code 42" in error_message110assert "stderr='test error message" in error_message111112# Extract and verify the stderr content is properly captured113import re114stderr_match = re.search(r"stderr='([^']*)'", error_message)115assert stderr_match is not None, "Could not find stderr in error message"116stderr_content = stderr_match.group(1).strip()117assert stderr_content == "test error message"118119def test_list_jupyter_kernels(self, project_client):120"""Test listing Jupyter kernels in a project."""121result = project_client.system.list_jupyter_kernels()122assert isinstance(result, list)123print(f"✓ Found {len(result)} Jupyter kernels")124# Each kernel should have basic properties125for kernel in result:126assert "pid" in kernel127assert isinstance(kernel["pid"], int)128assert kernel["pid"] > 0129130@pytest.mark.skipif(os.environ.get("CI") == "true", reason="Jupyter tests skipped in CI due to environment constraints")131def test_stop_jupyter_kernel(self, project_client):132"""Test stopping a Jupyter kernel.133134Note: Skipped in CI environments due to unreliable Jupyter setup.135"""136from tests.conftest import retry_with_backoff137138# First, execute code to ensure a kernel is running with retry139retry_with_backoff(lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3"))140141# List kernels142kernels = project_client.system.list_jupyter_kernels()143assert len(kernels) > 0, "Expected at least one kernel to be running"144145# Stop the first kernel146pid = kernels[0]["pid"]147result = project_client.system.stop_jupyter_kernel(pid=pid)148assert isinstance(result, dict)149assert "success" in result150print(f"✓ Stopped Jupyter kernel with PID {pid}: {result}")151152153