Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion sdk/ml/azure-ai-ml/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ml/azure-ai-ml",
"Tag": "python/ml/azure-ai-ml_1e2cb117b2"
"Tag": "python/ml/azure-ai-ml_a0b8a8b7"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import Callable
from pathlib import Path

import pytest
from devtools_testutils import AzureRecordedTestCase

from azure.ai.ml import (
MLClient,
load_batch_deployment,
load_batch_endpoint,
load_environment,
load_model,
)
from azure.ai.ml.entities import (
BatchDeployment,
PipelineComponent,
PipelineJob,
BatchEndpoint,
)
from azure.ai.ml._utils._arm_id_utils import AMLVersionedArmId
from azure.ai.ml.constants._common import AssetTypes
from azure.core.exceptions import HttpResponseError
from azure.ai.ml.exceptions import ValidationException


@pytest.mark.e2etest
@pytest.mark.usefixtures("recorded_test")
class TestBatchDeploymentGaps(AzureRecordedTestCase):
def test_begin_create_or_update_invalid_scoring_script_raises(
self,
client: MLClient,
randstr: Callable[[], str],
rand_batch_name: Callable[[], str],
rand_batch_deployment_name: Callable[[], str],
) -> None:
# This test triggers the validate_scoring_script branch by providing a deployment
# whose code configuration points to a local script path that does not exist.
# The call should raise an exception from validation before attempting REST calls.
deployment_yaml = "./tests/test_configs/deployments/batch/batch_deployment_quick.yaml"
name = rand_batch_deployment_name("deploy_name")
endpoint_name = rand_batch_name("endpoint_name")

deployment = load_batch_deployment(deployment_yaml)
deployment.name = name
deployment.endpoint_name = endpoint_name

# Ensure the deployment has a code configuration that references a non-ARM path
# so validate_scoring_script will be invoked. The test expects a validation error.
with pytest.raises((ValidationException, HttpResponseError)):
# begin_create_or_update will attempt validation and should raise
poller = client.batch_deployments.begin_create_or_update(deployment)
# If it doesn't raise immediately, wait on poller to surface errors
poller.result()

def test_validate_component_handles_missing_registered_component_and_creates(
self,
client: MLClient,
randstr: Callable[[], str],
rand_batch_name: Callable[[], str],
rand_batch_deployment_name: Callable[[], str],
) -> None:
# This test exercises _validate_component branch where deployment.component is a PipelineComponent
# and the registered component is not found; the operations should attempt to create one.
# We build a deployment from YAML and set its component to an inline PipelineComponent.
endpoint_yaml = "./tests/test_configs/endpoints/batch/batch_endpoint_mlflow_new.yaml"
deployment_yaml = "./tests/test_configs/deployments/batch/batch_deployment_quick.yaml"

endpoint = load_batch_endpoint(endpoint_yaml)
# Ensure endpoint name meets validation: starts with a letter and contains only alphanumerics and '-'
endpoint.name = rand_batch_name("endpoint_name2")

deployment = load_batch_deployment(deployment_yaml)
# Ensure deployment name meets validation rules as well
deployment.name = rand_batch_deployment_name("deploy_name2")
deployment.endpoint_name = endpoint.name

# Replace deployment.component with an anonymous PipelineComponent-like object
# that will trigger the create_or_update path inside _validate_component.
# Using PipelineComponent to match isinstance checks.
deployment.component = PipelineComponent()

# Create endpoint first so the deployment creation proceeds to component validation.
endpoint_poller = client.batch_endpoints.begin_create_or_update(endpoint)
endpoint_poller.result()

# Now attempt to create/update the deployment. If component creation fails due to
# service constraints, ensure the exception type is surfaced (HttpResponseError or similar).
try:
poller = client.batch_deployments.begin_create_or_update(deployment)
# Wait for result to ensure component creation branch is exercised.
poller.result()
except Exception as err:
# The important part is that an exception originates from the create_or_update flow
# (e.g., HttpResponseError) rather than a local programming error.
assert isinstance(err, HttpResponseError)
finally:
# Cleanup endpoint
client.batch_endpoints.begin_delete(name=endpoint.name).result()
98 changes: 98 additions & 0 deletions sdk/ml/azure-ai-ml/tests/test_batch_endpoint_operations_gaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import Callable

import pytest
from devtools_testutils import AzureRecordedTestCase

from azure.ai.ml import MLClient, load_batch_endpoint
from azure.ai.ml.entities._inputs_outputs import Input
from azure.ai.ml.exceptions import ValidationException, MlException
from azure.core.exceptions import ResourceNotFoundError


@pytest.mark.e2etest
@pytest.mark.usefixtures("recorded_test")
class TestBatchEndpointGaps(AzureRecordedTestCase):
def test_invoke_with_nonexistent_deployment_name_raises_validation_exception(
self, client: MLClient, rand_batch_name: Callable[[], str]
) -> None:
"""
Covers: marker lines related to deployment name validation paths in _validate_deployment_name.
Trigger strategy: create a batch endpoint, do not create any deployments, then call invoke with a
deployment_name that does not exist to force a ValidationException from _validate_deployment_name.
"""
endpoint_yaml = "./tests/test_configs/endpoints/batch/simple_batch_endpoint.yaml"
name = rand_batch_name("name")

endpoint = load_batch_endpoint(endpoint_yaml)
endpoint.name = name
# create the batch endpoint
obj = client.batch_endpoints.begin_create_or_update(endpoint=endpoint)
obj = obj.result()
assert obj is not None
assert obj.name == name

# Invoke with a deployment name that doesn't exist; this should raise a ValidationException
with pytest.raises(ValidationException):
client.batch_endpoints.invoke(endpoint_name=name, deployment_name="nonexistent_deployment")

# cleanup
delete_res = client.batch_endpoints.begin_delete(name=name)
delete_res = delete_res.result()
try:
client.batch_endpoints.get(name=name)
except Exception as e:
assert type(e) is ResourceNotFoundError
return
raise Exception(f"Batch endpoint {name} is supposed to be deleted.")

def test_invoke_with_empty_input_path_raises_mlexception(
self, client: MLClient, rand_batch_name: Callable[[], str]
) -> None:
"""
Covers: marker lines related to _resolve_input raising MlException when input.path is empty.
Trigger strategy: create a batch endpoint and call invoke with input=Input(path="") to trigger validation.
"""
endpoint_yaml = "./tests/test_configs/endpoints/batch/simple_batch_endpoint.yaml"
name = rand_batch_name("name")

endpoint = load_batch_endpoint(endpoint_yaml)
endpoint.name = name
# create the batch endpoint
obj = client.batch_endpoints.begin_create_or_update(endpoint=endpoint)
obj = obj.result()
assert obj is not None
assert obj.name == name

empty_input = Input(type="uri_folder", path="")
with pytest.raises(MlException):
client.batch_endpoints.invoke(endpoint_name=name, input=empty_input)

# cleanup
delete_res = client.batch_endpoints.begin_delete(name=name)
delete_res = delete_res.result()
try:
client.batch_endpoints.get(name=name)
except Exception as e:
assert type(e) is ResourceNotFoundError
return
raise Exception(f"Batch endpoint {name} is supposed to be deleted.")


@pytest.mark.e2etest
@pytest.mark.usefixtures("recorded_test")
class TestBatchEndpointGaps_Generated(AzureRecordedTestCase):
def test_list_jobs_returns_list(self, client: MLClient, rand_batch_name: Callable[[], str]) -> None:
endpoint_yaml = "./tests/test_configs/endpoints/batch/simple_batch_endpoint.yaml"
endpoint_name = rand_batch_name("endpoint_name")
endpoint = load_batch_endpoint(endpoint_yaml)
endpoint.name = endpoint_name

# create the batch endpoint
client.batch_endpoints.begin_create_or_update(endpoint).result()

# list_jobs should return a list (possibly empty)
result = client.batch_endpoints.list_jobs(endpoint_name=endpoint_name)
assert isinstance(result, list)

# cleanup
client.batch_endpoints.begin_delete(name=endpoint_name).result()
Loading