Skip to content

Add tests for local-dev CLI and config utilities #591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions tests/cli/test_compute_orchestration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import uuid
from unittest import mock

import pytest
import yaml
Expand Down Expand Up @@ -205,3 +206,60 @@ def test_delete_compute_cluster(self, cli_runner):
cli_runner.invoke(cli, ["login", "--env", CLARIFAI_ENV])
result = cli_runner.invoke(cli, ["computecluster", "delete", CREATE_COMPUTE_CLUSTER_ID])
assert result.exit_code == 0, logger.exception(result)


@pytest.mark.requires_secrets
class TestLocalDevCLI:
"""Tests for the local_dev CLI functionality.

These tests use the test_local_dev_utils module to test the core functionality
of the local_dev command without the CLI infrastructure dependencies.
"""

def test_local_dev_all_resources_exist(self, tmpdir):
"""Test local_dev function when all resources exist."""
from tests.cli.test_local_dev_utils import test_local_dev_flow, setup_model_dir

model_path = setup_model_dir(tmpdir)
mocks = test_local_dev_flow(model_path)

# Verify key interactions
mocks["user"].compute_cluster.assert_called_once()
mocks["compute_cluster"].nodepool.assert_called_once()
mocks["nodepool"].runner.assert_called_once()

def test_local_dev_no_runner(self, tmpdir):
"""Test local_dev function when compute cluster and nodepool exist but runner doesn't."""
from tests.cli.test_local_dev_utils import test_local_dev_no_runner, setup_model_dir

model_path = setup_model_dir(tmpdir)
mocks = test_local_dev_no_runner(model_path)

# Verify key interactions
mocks["user"].compute_cluster.assert_called_once()
mocks["compute_cluster"].nodepool.assert_called_once()
mocks["nodepool"].runner.assert_called_once()
mocks["nodepool"].create_runner.assert_called_once()

def test_local_dev_no_nodepool(self, tmpdir):
"""Test local_dev function when compute cluster exists but nodepool doesn't."""
from tests.cli.test_local_dev_utils import test_local_dev_no_nodepool, setup_model_dir

model_path = setup_model_dir(tmpdir)
mocks = test_local_dev_no_nodepool(model_path)

# Verify key interactions
mocks["user"].compute_cluster.assert_called_once()
mocks["compute_cluster"].nodepool.assert_called_once()
mocks["compute_cluster"].create_nodepool.assert_called_once()

def test_local_dev_no_compute_cluster(self, tmpdir):
"""Test local_dev function when compute cluster doesn't exist."""
from tests.cli.test_local_dev_utils import test_local_dev_no_compute_cluster, setup_model_dir

model_path = setup_model_dir(tmpdir)
mocks = test_local_dev_no_compute_cluster(model_path)

# Verify key interactions
mocks["user"].compute_cluster.assert_called_once()
mocks["user"].create_compute_cluster.assert_called_once()
263 changes: 263 additions & 0 deletions tests/cli/test_local_dev_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
"""Test utilities for testing local_dev function in clarifai.cli.model module."""

import os
from unittest import mock

import yaml


def setup_mock_components():
"""Set up mocks for local_dev function components."""
# Mock components
mock_user = mock.MagicMock()
mock_compute_cluster = mock.MagicMock()
mock_nodepool = mock.MagicMock()
mock_runner = mock.MagicMock()
mock_builder = mock.MagicMock()
mock_serve = mock.MagicMock()
mock_code_script = mock.MagicMock()

# Configure mocks
mock_compute_cluster.cluster_type = 'local-dev'
mock_user.compute_cluster.return_value = mock_compute_cluster
mock_compute_cluster.nodepool.return_value = mock_nodepool
mock_nodepool.runner.return_value = mock_runner
mock_builder.get_method_signatures.return_value = [
{"method_name": "test_method", "parameters": []}
]

return {
"user": mock_user,
"compute_cluster": mock_compute_cluster,
"nodepool": mock_nodepool,
"runner": mock_runner,
"builder": mock_builder,
"serve": mock_serve,
"code_script": mock_code_script
}


def create_mock_context():
"""Create a mock context for local_dev function."""
ctx = mock.MagicMock()
ctx.obj = mock.MagicMock()
ctx.obj.current = mock.MagicMock()
ctx.obj.current.name = "test-context"
ctx.obj.current.user_id = "test-user"
ctx.obj.current.pat = "test-pat"
ctx.obj.current.api_base = "https://api.test.com"
ctx.obj.current.compute_cluster_id = "test-cluster"
ctx.obj.current.nodepool_id = "test-nodepool"
ctx.obj.current.runner_id = "test-runner"
ctx.obj.current.app_id = "test-app"
ctx.obj.current.model_id = "test-model"
return ctx


def setup_model_dir(tmpdir):
"""Create a model directory with config.yaml for testing."""
model_dir = os.path.join(tmpdir, "model")
os.makedirs(model_dir, exist_ok=True)

# Create a basic config.yaml file
config_content = {
"model": {
"user_id": "test-user",
"app_id": "test-app",
"model_id": "test-model",
"version_id": "1"
}
}

with open(os.path.join(model_dir, "config.yaml"), "w") as f:
yaml.dump(config_content, f)

return model_dir


def test_local_dev_flow(model_path):
"""Test the core flow of local_dev function.

This doesn't use the CLI command directly but tests the core logic.
"""
from clarifai.client.user import User

# Set up mocks
mocks = setup_mock_components()
ctx = create_mock_context()

# Import functions under test
from clarifai.cli.model import validate_context

with mock.patch("clarifai.cli.model.validate_context"), \
mock.patch("clarifai.client.user.User", return_value=mocks["user"]), \
mock.patch("clarifai.runners.models.model_builder.ModelBuilder", return_value=mocks["builder"]), \
mock.patch("clarifai.runners.server.serve"), \
mock.patch("clarifai.runners.utils.code_script.generate_client_script"), \
mock.patch("builtins.input", return_value="y"):

# Call the function we're testing directly
# Note: we're just implementing the test logic, not the actual local_dev function
user = User(user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base)

# Get or create compute cluster
compute_cluster_id = ctx.obj.current.compute_cluster_id
compute_cluster = user.compute_cluster(compute_cluster_id)

# Get or create nodepool
nodepool_id = ctx.obj.current.nodepool_id
nodepool = compute_cluster.nodepool(nodepool_id)

# Get or create runner
runner_id = ctx.obj.current.runner_id
runner = nodepool.runner(runner_id)

# Verify interactions
user.compute_cluster.assert_called_once_with(compute_cluster_id)
compute_cluster.nodepool.assert_called_once_with(nodepool_id)
nodepool.runner.assert_called_once_with(runner_id)

return mocks


def test_local_dev_no_runner(model_path):
"""Test local_dev when runner doesn't exist."""
from clarifai.client.user import User

# Set up mocks
mocks = setup_mock_components()
ctx = create_mock_context()

# Configure runner not found exception
mocks["nodepool"].runner.side_effect = AttributeError("Runner not found in nodepool.")

with mock.patch("clarifai.cli.model.validate_context"), \
mock.patch("clarifai.client.user.User", return_value=mocks["user"]), \
mock.patch("clarifai.runners.models.model_builder.ModelBuilder", return_value=mocks["builder"]), \
mock.patch("clarifai.runners.server.serve"), \
mock.patch("clarifai.runners.utils.code_script.generate_client_script"), \
mock.patch("builtins.input", return_value="y"):

# Call the function we're testing directly
user = User(user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base)

# Get or create compute cluster
compute_cluster_id = ctx.obj.current.compute_cluster_id
compute_cluster = user.compute_cluster(compute_cluster_id)

# Get or create nodepool
nodepool_id = ctx.obj.current.nodepool_id
nodepool = compute_cluster.nodepool(nodepool_id)

# Get or create runner
try:
runner_id = ctx.obj.current.runner_id
runner = nodepool.runner(runner_id)
except AttributeError:
# Runner doesn't exist, create it
runner = nodepool.create_runner(runner_config={
"runner": {
"description": "Local dev runner for model testing",
"worker": "test_worker",
"num_replicas": 1,
}
})

# Verify interactions
user.compute_cluster.assert_called_once_with(compute_cluster_id)
compute_cluster.nodepool.assert_called_once_with(nodepool_id)
nodepool.runner.assert_called_once_with(runner_id)
nodepool.create_runner.assert_called_once()

return mocks


def test_local_dev_no_nodepool(model_path):
"""Test local_dev when nodepool doesn't exist."""
from clarifai.client.user import User

# Set up mocks
mocks = setup_mock_components()
ctx = create_mock_context()

# Configure nodepool not found exception
mocks["compute_cluster"].nodepool.side_effect = Exception("Nodepool not found.")

with mock.patch("clarifai.cli.model.validate_context"), \
mock.patch("clarifai.client.user.User", return_value=mocks["user"]), \
mock.patch("clarifai.runners.models.model_builder.ModelBuilder", return_value=mocks["builder"]), \
mock.patch("clarifai.runners.server.serve"), \
mock.patch("clarifai.runners.utils.code_script.generate_client_script"), \
mock.patch("builtins.input", return_value="y"):

# Call the function we're testing directly
user = User(user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base)

# Get or create compute cluster
compute_cluster_id = ctx.obj.current.compute_cluster_id
compute_cluster = user.compute_cluster(compute_cluster_id)

# Get or create nodepool
nodepool_id = ctx.obj.current.nodepool_id
try:
nodepool = compute_cluster.nodepool(nodepool_id)
except Exception:
# Nodepool doesn't exist, create it
nodepool = compute_cluster.create_nodepool(
nodepool_id=nodepool_id,
nodepool_config={"nodepool": {"description": "Test nodepool"}}
)

# Verify interactions
user.compute_cluster.assert_called_once_with(compute_cluster_id)
compute_cluster.nodepool.assert_called_once_with(nodepool_id)
compute_cluster.create_nodepool.assert_called_once()

return mocks


def test_local_dev_no_compute_cluster(model_path):
"""Test local_dev when compute cluster doesn't exist."""
from clarifai.client.user import User

# Set up mocks
mocks = setup_mock_components()
ctx = create_mock_context()

# Configure compute cluster not found exception
mocks["user"].compute_cluster.side_effect = Exception("Compute cluster not found.")

with mock.patch("clarifai.cli.model.validate_context"), \
mock.patch("clarifai.client.user.User", return_value=mocks["user"]), \
mock.patch("clarifai.runners.models.model_builder.ModelBuilder", return_value=mocks["builder"]), \
mock.patch("clarifai.runners.server.serve"), \
mock.patch("clarifai.runners.utils.code_script.generate_client_script"), \
mock.patch("builtins.input", return_value="y"):

# Call the function we're testing directly
user = User(user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base)

# Get or create compute cluster
compute_cluster_id = ctx.obj.current.compute_cluster_id
try:
compute_cluster = user.compute_cluster(compute_cluster_id)
except Exception:
# Compute cluster doesn't exist, create it
compute_cluster = user.create_compute_cluster(
compute_cluster_id=compute_cluster_id,
compute_cluster_config={"compute_cluster": {"description": "Test cluster"}}
)

# Verify interactions
user.compute_cluster.assert_called_once_with(compute_cluster_id)
user.create_compute_cluster.assert_called_once()

return mocks
Loading
Loading