Files
comet-ml--opik/sdks/python/tests/unit/cli/test_cli.py
T
wehub-resource-sync 5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

446 lines
17 KiB
Python

"""Tests for CLI commands."""
from pathlib import Path
from unittest.mock import MagicMock, patch
from contextlib import contextmanager
from click.testing import CliRunner
from opik.cli import cli
class TestDownloadCommand:
"""Test the download CLI command."""
def test_export_group_help(self):
"""Test that the export group shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["export", "--help"])
assert result.exit_code == 0
assert "Export data from an Opik project" in result.output
assert "dataset" in result.output
assert "traces" in result.output
assert "experiment" in result.output
def test_export_dataset_help(self):
"""Test that the export dataset command shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["export", "default", "proj", "dataset", "--help"])
assert result.exit_code == 0
assert "Export a dataset by exact name" in result.output
assert "--force" in result.output
def test_export_traces_help(self):
"""Test that the export traces command shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["export", "default", "proj", "traces", "--help"])
assert result.exit_code == 0
assert "Export the project's traces" in result.output
def test_export_experiment_help(self):
"""Test that the export experiment command shows help."""
runner = CliRunner()
result = runner.invoke(
cli, ["export", "default", "proj", "experiment", "--help"]
)
assert result.exit_code == 0
assert "Export an experiment by exact name" in result.output
class TestUploadCommand:
"""Test the upload CLI command."""
def test_import_group_help(self):
"""Test that the import group shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["import", "--help"])
assert result.exit_code == 0
assert "Import data into an Opik project" in result.output
assert "dataset" in result.output
assert "traces" in result.output
assert "experiment" in result.output
def test_import_dataset_help(self):
"""Test that the import dataset command shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["import", "default", "proj", "dataset", "--help"])
assert result.exit_code == 0
assert "Import datasets from" in result.output
assert "--dry-run" in result.output
def test_import_traces_help(self):
"""Test that the import traces command shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["import", "default", "proj", "traces", "--help"])
assert result.exit_code == 0
assert "Import the project's traces" in result.output
assert "--dry-run" in result.output
def test_import_experiment_help(self):
"""Test that the import experiment command shows help."""
runner = CliRunner()
result = runner.invoke(
cli, ["import", "default", "proj", "experiment", "--help"]
)
assert result.exit_code == 0
assert "Import experiments from" in result.output
assert "--dry-run" in result.output
class TestSmokeTestCommand:
"""Test the smoke-test functionality via healthcheck command."""
def test_smoke_test_help(self):
"""Test that the healthcheck --smoke-test command shows help."""
runner = CliRunner()
result = runner.invoke(cli, ["healthcheck", "--help"])
assert result.exit_code == 0
assert "--smoke-test" in result.output
assert "--project-name" in result.output
assert "Project name for the smoke test" in result.output
assert "WORKSPACE" in result.output
assert "Run a smoke test to verify Opik integration" in result.output
def test_smoke_test_minimal_args_parsing(self):
"""Test that healthcheck --smoke-test command requires workspace value."""
runner = CliRunner()
# Test that help shows workspace is required
result = runner.invoke(cli, ["healthcheck", "--help"])
assert result.exit_code == 0
assert "WORKSPACE" in result.output
# Test that missing workspace value causes error
result = runner.invoke(cli, ["healthcheck", "--smoke-test"])
assert result.exit_code != 0
assert "Error" in result.output or "Missing" in result.output
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.smoke_test.opik.Opik")
@patch("opik.cli.healthcheck.smoke_test.opik.start_as_current_trace")
@patch("opik.cli.healthcheck.smoke_test.opik_context.update_current_trace")
@patch("opik.cli.healthcheck.smoke_test.opik_context.update_current_span")
@patch("opik.cli.healthcheck.smoke_test.create_opik_logo_image")
@patch("opik.cli.healthcheck.smoke_test.track")
def test_smoke_test_with_workspace_and_project_name(
self,
mock_track,
mock_create_logo,
mock_update_span,
mock_update_trace,
mock_start_trace,
mock_opik_class,
mock_healthcheck_run,
):
"""Test healthcheck --smoke-test command with workspace and --project-name arguments."""
# Setup mocks
mock_client = MagicMock()
# Mock search_traces to raise an exception immediately to skip verification polling
# This prevents the verification from running and triggering real client creation
mock_client.search_traces = MagicMock(
side_effect=AttributeError("Mock client - search_traces not available")
)
mock_opik_class.return_value = mock_client
# Mock the context manager for start_as_current_trace
@contextmanager
def mock_trace_context(*args, **kwargs):
yield MagicMock()
mock_start_trace.return_value = mock_trace_context()
# Mock create_opik_logo_image to return a fake path
mock_logo_path = Path("/tmp/fake_logo.png")
mock_create_logo.return_value = mock_logo_path
# Mock the track decorator to return the function unchanged
def track_decorator(*args, **kwargs):
def decorator(func):
return func
return decorator
mock_track.side_effect = track_decorator
# Run the command
runner = CliRunner()
result = runner.invoke(
cli,
[
"healthcheck",
"--smoke-test",
"test-workspace",
"--project-name",
"test-project",
],
catch_exceptions=False,
)
# Assertions
assert result.exit_code == 0
# Verify client was created with correct arguments
mock_opik_class.assert_called_once()
call_kwargs = mock_opik_class.call_args[1]
assert call_kwargs["workspace"] == "test-workspace"
assert call_kwargs["project_name"] == "test-project"
# Verify trace was started
mock_start_trace.assert_called_once()
# Verify explicit client was flushed and ended
mock_client.flush.assert_called_once()
mock_client.end.assert_called_once()
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.smoke_test.opik.Opik")
@patch("opik.cli.healthcheck.smoke_test.opik.start_as_current_trace")
@patch("opik.cli.healthcheck.smoke_test.opik_context.update_current_trace")
@patch("opik.cli.healthcheck.smoke_test.opik_context.update_current_span")
@patch("opik.cli.healthcheck.smoke_test.create_opik_logo_image")
@patch("opik.cli.healthcheck.smoke_test.track")
def test_smoke_test_with_default_project_name(
self,
mock_track,
mock_create_logo,
mock_update_span,
mock_update_trace,
mock_start_trace,
mock_opik_class,
mock_healthcheck_run,
):
"""Test healthcheck --smoke-test command with workspace only (uses default project name)."""
# Setup mocks
mock_client = MagicMock()
# Mock search_traces to raise an exception immediately to skip verification polling
# This prevents the verification from running and triggering real client creation
mock_client.search_traces = MagicMock(
side_effect=AttributeError("Mock client - search_traces not available")
)
mock_opik_class.return_value = mock_client
# Mock the context manager for start_as_current_trace
@contextmanager
def mock_trace_context(*args, **kwargs):
yield MagicMock()
mock_start_trace.return_value = mock_trace_context()
# Mock create_opik_logo_image to return a fake path
mock_logo_path = Path("/tmp/fake_logo.png")
mock_create_logo.return_value = mock_logo_path
# Mock the track decorator to return the function unchanged
def track_decorator(*args, **kwargs):
def decorator(func):
return func
return decorator
mock_track.side_effect = track_decorator
# Run the command without --project-name (should use default)
runner = CliRunner()
result = runner.invoke(
cli,
["healthcheck", "--smoke-test", "test-workspace"],
catch_exceptions=False,
)
# Assertions
assert result.exit_code == 0
# Verify client was created with default project name
mock_opik_class.assert_called_once()
call_kwargs = mock_opik_class.call_args[1]
assert call_kwargs["workspace"] == "test-workspace"
assert call_kwargs["project_name"] == "smoke-test-project" # Default value
# Verify trace was started with default project name
mock_start_trace.assert_called_once()
trace_call_kwargs = mock_start_trace.call_args[1]
assert trace_call_kwargs["project_name"] == "smoke-test-project"
class TestCheckPermissionsCommand:
"""Test the --check-permissions functionality via healthcheck command."""
def test_check_permissions_option__in_healthcheck_help__shown(self):
"""Test that --check-permissions option is shown in healthcheck help."""
runner = CliRunner()
result = runner.invoke(cli, ["healthcheck", "--help"])
assert result.exit_code == 0
assert "--check-permissions" in result.output
assert "WORKSPACE" in result.output
def test_check_permissions__missing_workspace__raises_error(self):
"""Test that --check-permissions without a workspace value raises an error."""
runner = CliRunner()
result = runner.invoke(cli, ["healthcheck", "--check-permissions"])
assert result.exit_code != 0
assert "Error" in result.output or "Missing" in result.output
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.cli.check_user_permissions.run")
def test_check_permissions__workspace_provided__calls_run_with_workspace(
self,
mock_permissions_run,
mock_standard_run,
):
"""Test that --check-permissions pass the workspace to check_user_permissions.run."""
runner = CliRunner()
result = runner.invoke(
cli,
["healthcheck", "--check-permissions", "my-workspace"],
catch_exceptions=False,
)
assert result.exit_code == 0
mock_permissions_run.assert_called_once_with(
api_key=None, workspace="my-workspace"
)
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.cli.check_user_permissions.run")
def test_check_permissions__api_key_in_cli_context__passes_api_key_to_run(
self,
mock_permissions_run,
mock_standard_run,
):
"""Test that --check-permissions forward the api_key from CLI context."""
from opik.cli import cli as opik_cli
runner = CliRunner()
result = runner.invoke(
opik_cli,
[
"--api-key",
"test-api-key",
"healthcheck",
"--check-permissions",
"my-workspace",
],
catch_exceptions=False,
)
assert result.exit_code == 0
mock_permissions_run.assert_called_once_with(
api_key="test-api-key", workspace="my-workspace"
)
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.cli.check_user_permissions.run")
def test_check_permissions__empty_workspace__raises_bad_parameter(
self,
mock_permissions_run,
mock_standard_run,
):
"""Test that --check-permissions with an empty workspace string raises BadParameter."""
runner = CliRunner()
result = runner.invoke(
cli,
["healthcheck", "--check-permissions", ""],
)
assert result.exit_code != 0
assert (
"Error" in result.output
or "requires a non-empty workspace name" in result.output
)
mock_permissions_run.assert_not_called()
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.cli.check_user_permissions.run")
def test_check_permissions__no_flag__not_called(
self,
mock_permissions_run,
mock_standard_run,
):
"""Test that check_user_permissions.run is not called when --check-permissions is absent."""
runner = CliRunner()
result = runner.invoke(cli, ["healthcheck"], catch_exceptions=False)
assert result.exit_code == 0
mock_permissions_run.assert_not_called()
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.check_user_permissions.get_user_permissions")
@patch("opik.cli.healthcheck.check_user_permissions.config.OpikConfig")
def test_check_permissions__api_returns_user_and_workspace__displays_info(
self,
mock_opik_config,
mock_get_permissions,
mock_standard_run,
):
"""Test that user and workspace info from API response are printed."""
mock_config = MagicMock()
mock_config.api_key = "test-api-key"
mock_config.workspace = "my-workspace"
mock_config.url_override = "https://opik.example.com"
mock_opik_config.return_value = mock_config
mock_get_permissions.return_value = {
"user_name": "alice",
"workspace_name": "my-workspace",
"permissions": [
{"permission_name": "read", "permission_value": True},
{"permission_name": "write", "permission_value": False},
],
}
runner = CliRunner()
result = runner.invoke(
cli,
["healthcheck", "--check-permissions", "my-workspace"],
catch_exceptions=False,
)
assert result.exit_code == 0
assert "alice" in result.output
assert "my-workspace" in result.output
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.check_user_permissions.get_user_permissions")
@patch("opik.cli.healthcheck.check_user_permissions.config.OpikConfig")
def test_check_permissions__connection_error__prints_error(
self,
mock_opik_config,
mock_get_permissions,
mock_standard_run,
):
"""Test that a ConnectionError from the API prints an error message."""
mock_config = MagicMock()
mock_config.api_key = "test-api-key"
mock_config.workspace = "my-workspace"
mock_config.url_override = "https://opik.example.com"
mock_opik_config.return_value = mock_config
mock_get_permissions.side_effect = ConnectionError("Network error: timeout")
runner = CliRunner()
result = runner.invoke(
cli,
["healthcheck", "--check-permissions", "my-workspace"],
catch_exceptions=False,
)
assert result.exit_code == 0
assert "Failed to fetch user permissions" in result.output
@patch("opik.cli.healthcheck.cli.standard_check.run")
@patch("opik.cli.healthcheck.check_user_permissions.config.OpikConfig")
def test_check_permissions__missing_api_key__raises_value_error(
self,
mock_opik_config,
mock_standard_run,
):
"""Test that missing api_key (not in CLI context and not in config) raises ValueError."""
mock_config = MagicMock()
mock_config.api_key = None
mock_config.workspace = None
mock_config.url_override = "https://opik.example.com"
mock_opik_config.return_value = mock_config
runner = CliRunner()
result = runner.invoke(
cli,
["healthcheck", "--check-permissions", "my-workspace"],
)
assert result.exit_code != 0
assert isinstance(result.exception, ValueError)
assert "API key is required" in str(result.exception)