e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
207 lines
7.8 KiB
Python
207 lines
7.8 KiB
Python
# Copyright 2025 Alibaba Group Holding Ltd.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from opensandbox_server.services.k8s.batchsandbox_template import BatchSandboxTemplateManager
|
|
|
|
class TestBatchSandboxTemplateManager:
|
|
|
|
def test_load_valid_yaml_template_successfully(self, tmp_path):
|
|
# Create valid template file
|
|
template_file = tmp_path / "valid_template.yaml"
|
|
template_content = {
|
|
"metadata": {"annotations": {"test": "value"}},
|
|
"spec": {"template": {"spec": {"nodeSelector": {"env": "test"}}}}
|
|
}
|
|
template_file.write_text(yaml.dump(template_content))
|
|
|
|
manager = BatchSandboxTemplateManager(str(template_file))
|
|
|
|
assert manager._template == template_content
|
|
assert manager.template_file_path == str(template_file)
|
|
|
|
def test_load_nonexistent_file_raises_error(self):
|
|
# Should raise FileNotFoundError
|
|
with pytest.raises(FileNotFoundError) as exc_info:
|
|
BatchSandboxTemplateManager("/path/to/nonexistent.yaml")
|
|
|
|
assert "not found" in str(exc_info.value)
|
|
|
|
def test_load_invalid_yaml_raises_error(self, tmp_path):
|
|
# Create malformed YAML
|
|
template_file = tmp_path / "invalid.yaml"
|
|
template_file.write_text("invalid: yaml: [missing: bracket")
|
|
|
|
# Should raise RuntimeError
|
|
with pytest.raises(RuntimeError) as exc_info:
|
|
BatchSandboxTemplateManager(str(template_file))
|
|
|
|
assert "Failed to load" in str(exc_info.value)
|
|
|
|
def test_load_non_dict_yaml_raises_error(self, tmp_path):
|
|
# Create YAML with list
|
|
template_file = tmp_path / "list.yaml"
|
|
template_file.write_text("- item1\n- item2")
|
|
|
|
# Should raise ValueError
|
|
with pytest.raises(ValueError) as exc_info:
|
|
BatchSandboxTemplateManager(str(template_file))
|
|
|
|
assert "must be a YAML object" in str(exc_info.value)
|
|
assert "got list" in str(exc_info.value)
|
|
|
|
def test_init_without_template_file_creates_empty_manager(self):
|
|
manager = BatchSandboxTemplateManager(None)
|
|
|
|
assert manager._template is None
|
|
assert manager.template_file_path is None
|
|
|
|
def test_deep_merge_runtime_overrides_template(self):
|
|
base = {"spec": {"replicas": 1, "expireTime": "old"}}
|
|
override = {"spec": {"expireTime": "new"}}
|
|
|
|
result = BatchSandboxTemplateManager._deep_merge(base, override)
|
|
|
|
assert result == {"spec": {"replicas": 1, "expireTime": "new"}}
|
|
|
|
def test_deep_merge_preserves_template_only_fields(self):
|
|
base = {
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"nodeSelector": {"env": "prod"},
|
|
"tolerations": [{"key": "test"}]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
override = {"spec": {"replicas": 1}}
|
|
|
|
result = BatchSandboxTemplateManager._deep_merge(base, override)
|
|
|
|
assert result["spec"]["replicas"] == 1
|
|
assert result["spec"]["template"]["spec"]["nodeSelector"] == {"env": "prod"}
|
|
assert result["spec"]["template"]["spec"]["tolerations"] == [{"key": "test"}]
|
|
|
|
def test_deep_merge_nested_dicts_recursively(self):
|
|
base = {"metadata": {"annotations": {"a": "1", "b": "2"}}}
|
|
override = {"metadata": {"annotations": {"b": "3", "c": "4"}}}
|
|
|
|
result = BatchSandboxTemplateManager._deep_merge(base, override)
|
|
|
|
expected = {"metadata": {"annotations": {"a": "1", "b": "3", "c": "4"}}}
|
|
assert result == expected
|
|
|
|
def test_deep_merge_replaces_lists_not_merges(self):
|
|
base = {"spec": {"tolerations": [{"key": "a"}]}}
|
|
override = {"spec": {"tolerations": [{"key": "b"}]}}
|
|
|
|
result = BatchSandboxTemplateManager._deep_merge(base, override)
|
|
|
|
assert result == {"spec": {"tolerations": [{"key": "b"}]}}
|
|
|
|
def test_deep_merge_none_values_do_not_override(self):
|
|
base = {"spec": {"expireTime": "2024-12-31"}}
|
|
override = {"spec": {"expireTime": None}}
|
|
|
|
result = BatchSandboxTemplateManager._deep_merge(base, override)
|
|
|
|
assert result == {"spec": {"expireTime": "2024-12-31"}}
|
|
|
|
def test_deep_copy_creates_independent_copies(self):
|
|
original = {
|
|
"nested": {"list": [1, 2, 3], "dict": {"key": "value"}}
|
|
}
|
|
|
|
copy = BatchSandboxTemplateManager._deep_copy(original)
|
|
|
|
# Modify copy
|
|
copy["nested"]["list"].append(4)
|
|
copy["nested"]["dict"]["key"] = "new_value"
|
|
|
|
# Original should not be affected
|
|
assert original["nested"]["list"] == [1, 2, 3]
|
|
assert original["nested"]["dict"]["key"] == "value"
|
|
|
|
def test_get_base_template_returns_copy(self, tmp_path):
|
|
template_file = tmp_path / "template.yaml"
|
|
template_content = {"spec": {"replicas": 1}}
|
|
template_file.write_text(yaml.dump(template_content))
|
|
|
|
manager = BatchSandboxTemplateManager(str(template_file))
|
|
|
|
template1 = manager.get_base_template()
|
|
template2 = manager.get_base_template()
|
|
|
|
# Same content but not same object
|
|
assert template1 == template2
|
|
assert template1 is not template2
|
|
|
|
def test_get_base_template_returns_empty_dict_when_no_template(self):
|
|
manager = BatchSandboxTemplateManager(None)
|
|
|
|
result = manager.get_base_template()
|
|
|
|
assert result == {}
|
|
|
|
def test_merge_with_runtime_values_without_template(self):
|
|
manager = BatchSandboxTemplateManager(None)
|
|
runtime_manifest = {"spec": {"replicas": 1}}
|
|
|
|
result = manager.merge_with_runtime_values(runtime_manifest)
|
|
|
|
assert result == runtime_manifest
|
|
|
|
def test_merge_with_runtime_values_with_template(self, tmp_path):
|
|
# Create template
|
|
template_file = tmp_path / "template.yaml"
|
|
template_content = {
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"nodeSelector": {"workload": "sandbox"},
|
|
"tolerations": [{"operator": "Exists"}]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
template_file.write_text(yaml.dump(template_content))
|
|
|
|
manager = BatchSandboxTemplateManager(str(template_file))
|
|
|
|
# Runtime manifest
|
|
runtime_manifest = {
|
|
"spec": {
|
|
"replicas": 1,
|
|
"template": {
|
|
"spec": {
|
|
"containers": [{"name": "test"}],
|
|
"volumes": [{"name": "vol"}]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result = manager.merge_with_runtime_values(runtime_manifest)
|
|
|
|
# Verify template fields are preserved
|
|
assert result["spec"]["template"]["spec"]["nodeSelector"] == {"workload": "sandbox"}
|
|
assert result["spec"]["template"]["spec"]["tolerations"] == [{"operator": "Exists"}]
|
|
# Verify runtime fields are added
|
|
assert result["spec"]["replicas"] == 1
|
|
assert result["spec"]["template"]["spec"]["containers"] == [{"name": "test"}]
|
|
assert result["spec"]["template"]["spec"]["volumes"] == [{"name": "vol"}]
|