e0e362d700
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
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
211 lines
7.9 KiB
Python
211 lines
7.9 KiB
Python
# Copyright 2026 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.
|
|
|
|
"""Unit tests for DockerMetadataStore (per-sandbox file-backed)."""
|
|
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from opensandbox_server.services.docker.metadata import (
|
|
DockerMetadataStore,
|
|
_extract_user_labels,
|
|
_is_user_label,
|
|
)
|
|
|
|
|
|
class TestIsUserLabel:
|
|
def test_user_labels_return_true(self):
|
|
assert _is_user_label("team")
|
|
assert _is_user_label("app.kubernetes.io/name")
|
|
assert _is_user_label("example.com/label")
|
|
|
|
def test_opensandbox_prefix_returns_false(self):
|
|
assert not _is_user_label("opensandbox.io/id")
|
|
assert not _is_user_label("opensandbox.io/expires-at")
|
|
|
|
def test_oci_labels_return_false(self):
|
|
assert not _is_user_label("org.opencontainers.image.created")
|
|
assert not _is_user_label("org.opencontainers.image.version")
|
|
|
|
def test_docker_labels_return_false(self):
|
|
assert not _is_user_label("com.docker.some-label")
|
|
assert not _is_user_label("desktop.docker.other")
|
|
|
|
|
|
class TestExtractUserLabels:
|
|
def test_filters_non_user_labels(self):
|
|
labels = {
|
|
"team": "infra",
|
|
"opensandbox.io/id": "sbx-001",
|
|
"org.opencontainers.image.created": "2026-01-01",
|
|
"com.docker.foo": "bar",
|
|
}
|
|
result = _extract_user_labels(labels)
|
|
assert result == {"team": "infra"}
|
|
|
|
def test_empty_returns_empty(self):
|
|
assert _extract_user_labels({}) == {}
|
|
|
|
|
|
class TestDockerMetadataStore:
|
|
|
|
@staticmethod
|
|
def _make_store(tmp_path: Path):
|
|
return DockerMetadataStore(root=tmp_path / "metadata")
|
|
|
|
# -- get (no overrides) --------------------------------------------------
|
|
|
|
def test_get_no_overrides_returns_user_labels(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {
|
|
"team": "infra",
|
|
"opensandbox.io/id": "sbx-001",
|
|
"org.opencontainers.image.created": "2026-01-01",
|
|
}
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "infra"}
|
|
|
|
def test_get_no_user_labels_returns_none(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
result = store.get("sbx-001", {"opensandbox.io/id": "sbx-001"})
|
|
assert result is None
|
|
|
|
# -- patch ---------------------------------------------------------------
|
|
|
|
def test_patch_adds_keys(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "infra"}
|
|
store.patch("sbx-001", labels, {"project": "new"})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "infra", "project": "new"}
|
|
|
|
def test_patch_deletes_keys(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "infra", "project": "old"}
|
|
store.patch("sbx-001", labels, {"project": None})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "infra"}
|
|
|
|
def test_patch_replaces_keys(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "old-team"}
|
|
store.patch("sbx-001", labels, {"team": "new-team"})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "new-team"}
|
|
|
|
def test_patch_persists_across_instances(self, tmp_path: Path):
|
|
root = tmp_path / "metadata"
|
|
store1 = DockerMetadataStore(root=root)
|
|
store1.patch("sbx-001", {"hello": "world"}, {"team": "platform"})
|
|
|
|
store2 = DockerMetadataStore(root=root)
|
|
result = store2.get("sbx-001", {"hello": "world"})
|
|
assert result == {"hello": "world", "team": "platform"}
|
|
|
|
def test_patch_empty_noop(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "infra"}
|
|
store.patch("sbx-001", labels, {})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "infra"}
|
|
|
|
def test_patch_from_empty_labels(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels: dict = {}
|
|
store.patch("sbx-001", labels, {"new": "value"})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"new": "value"}
|
|
|
|
def test_patch_delete_nonexistent_silent(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "infra"}
|
|
store.patch("sbx-001", labels, {"nonexistent": None})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "infra"}
|
|
|
|
def test_patch_mixed_add_and_delete(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "infra", "env": "staging", "old": "remove"}
|
|
store.patch("sbx-001", labels, {"team": "platform", "env": None, "project": "new"})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "platform", "old": "remove", "project": "new"}
|
|
|
|
def test_patch_ignores_system_labels(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {
|
|
"hello": "world",
|
|
"opensandbox.io/id": "sbx-001",
|
|
"org.opencontainers.image.version": "26.04",
|
|
}
|
|
store.patch("sbx-001", labels, {"team": "platform"})
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"hello": "world", "team": "platform"}
|
|
|
|
# -- delete --------------------------------------------------------------
|
|
|
|
def test_delete_removes_file(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
labels = {"team": "infra"}
|
|
store.patch("sbx-001", labels, {"project": "new"})
|
|
|
|
f = tmp_path / "metadata" / "sbx-001.json"
|
|
assert f.exists()
|
|
|
|
store.delete("sbx-001")
|
|
assert not f.exists()
|
|
# Falls back to container labels
|
|
result = store.get("sbx-001", labels)
|
|
assert result == {"team": "infra"}
|
|
|
|
def test_delete_nonexistent_silent(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
store.delete("nonexistent") # Should not raise
|
|
|
|
def test_set_get_expiration_override(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
expires_at = datetime(2030, 1, 1, tzinfo=timezone.utc)
|
|
|
|
store.set_expiration("sbx-001", expires_at)
|
|
|
|
assert store.get_expiration("sbx-001") == expires_at.isoformat()
|
|
|
|
def test_delete_removes_expiration_override_file(self, tmp_path: Path):
|
|
store = self._make_store(tmp_path)
|
|
expires_at = datetime(2030, 1, 1, tzinfo=timezone.utc)
|
|
store.set_expiration("sbx-001", expires_at)
|
|
|
|
expiration_file = tmp_path / "metadata" / "_expiration" / "sbx-001.json"
|
|
assert expiration_file.exists()
|
|
|
|
store.delete("sbx-001")
|
|
|
|
assert not expiration_file.exists()
|
|
assert store.get_expiration("sbx-001") is None
|
|
|
|
# -- crash recovery ------------------------------------------------------
|
|
|
|
def test_tmp_file_not_read_on_reload(self, tmp_path: Path):
|
|
"""Tmp files written during atomic save must not be picked up by get()."""
|
|
root = tmp_path / "metadata"
|
|
store1 = DockerMetadataStore(root=root)
|
|
store1.patch("sbx-001", {"hello": "world"}, {"team": "platform"})
|
|
|
|
# Simulate a leftover .tmp file
|
|
tmp_file = root / "sbx-001.tmp"
|
|
tmp_file.write_text('{"bad": "data"}')
|
|
|
|
store2 = DockerMetadataStore(root=root)
|
|
result = store2.get("sbx-001", {"hello": "world"})
|
|
assert result == {"hello": "world", "team": "platform"}
|