Files
svcforge/tests/unit/test_catalog.py
T
Nguyen Minh Phuc 6974b3620f
ci / lint (push) Successful in 24s
ci / types (push) Successful in 35s
ci / unit (push) Successful in 26s
ci / security (push) Successful in 41s
ci / dockerfile (push) Successful in 8s
ci / chart (push) Successful in 9s
ci / integration (push) Successful in 46s
ci / image (api) (push) Successful in 2m32s
ci / image (reconciler) (push) Successful in 2m50s
ci / image (worker) (push) Successful in 2m43s
ci / bump (push) Successful in 21s
catalog: two tiny services, so the loop can be exercised on a full cluster
The three existing entries are sized like real products: `elasticsearch` small
asks for 1Gi and medium for 4Gi across three replicas. On this cluster that is
a request that never schedules, so provisioning them proves something about the
node and nothing about svcforge.

  podinfo  16Mi/10m  — one small Go binary, no dependencies, no PVC
  nginx    32Mi/10m  — recognisable, still small

Both are addressed as `oci://`, which is load-bearing rather than cosmetic. An
OCI chart is pulled by reference with no `helm repo add` first. The three
existing entries name `bitnamilegacy/<chart>`, a classic repo alias that
nothing in the worker image configures — so as written they cannot resolve at
provision time. OCI is the form that works from a bare container, and it is why
the e2e test already provisions podinfo.

Chart versions were resolved against the real registries before committing
(podinfo 6.7.1, nginx 25.0.14 / app 1.31.3) rather than guessed, since a wrong
version fails only at provision time.

Also updates the CLI ServiceType enum, the catalog test's expected set, and the
service list in the OpenAPI description.
2026-07-21 05:47:41 +00:00

156 lines
4.6 KiB
Python

"""Unit tests for catalog loading and validation."""
import textwrap
from pathlib import Path
import pytest
from svcforge_core.domain.catalog import CatalogError, load_catalog
from svcforge_core.domain.models import CatalogEntry
VALID_YAML = textwrap.dedent("""
services:
redis:
chart: bitnamilegacy/redis
chart_version: 20.6.2
sizes:
small:
replicas: 1
resources:
requests: {cpu: 100m, memory: 256Mi}
medium:
replicas: 3
resources:
requests: {cpu: 500m, memory: 1Gi}
postgres:
chart: bitnamilegacy/postgresql
chart_version: 16.4.5
sizes:
small:
replicas: 1
resources:
requests: {cpu: 250m, memory: 512Mi}
""")
MISSING_CHART_VERSION_YAML = textwrap.dedent("""
services:
redis:
chart: bitnamilegacy/redis
sizes:
small:
replicas: 1
resources: {}
""")
ZERO_REPLICAS_YAML = textwrap.dedent("""
services:
redis:
chart: bitnamilegacy/redis
chart_version: 20.6.2
sizes:
small:
replicas: 0
resources: {}
""")
def _write(tmp_path: Path, body: str) -> Path:
path = tmp_path / "catalog.yaml"
path.write_text(body)
return path
def test_valid_yaml_loads_to_catalog_entries(tmp_path: Path) -> None:
catalog = load_catalog(_write(tmp_path, VALID_YAML))
assert set(catalog) == {"redis", "postgres"}
assert all(isinstance(entry, CatalogEntry) for entry in catalog.values())
redis = catalog["redis"]
assert redis.service_type == "redis"
assert redis.chart_version == "20.6.2"
assert set(redis.sizes) == {"small", "medium"}
assert redis.sizes["medium"].replicas == 3
def test_missing_chart_version_raises_catalog_error(tmp_path: Path) -> None:
with pytest.raises(CatalogError) as excinfo:
load_catalog(_write(tmp_path, MISSING_CHART_VERSION_YAML))
assert excinfo.value.key == "redis"
assert "redis" in str(excinfo.value)
def test_zero_replicas_raises_catalog_error(tmp_path: Path) -> None:
with pytest.raises(CatalogError) as excinfo:
load_catalog(_write(tmp_path, ZERO_REPLICAS_YAML))
assert excinfo.value.key == "redis"
def test_missing_file_raises_catalog_error(tmp_path: Path) -> None:
with pytest.raises(CatalogError, match="cannot read catalog"):
load_catalog(tmp_path / "nope.yaml")
def test_unparseable_yaml_raises_catalog_error(tmp_path: Path) -> None:
with pytest.raises(CatalogError, match="not valid YAML"):
load_catalog(_write(tmp_path, "services: [unclosed\n"))
def test_scalar_root_raises_catalog_error(tmp_path: Path) -> None:
with pytest.raises(CatalogError, match="must be a mapping"):
load_catalog(_write(tmp_path, "just-a-string\n"))
def test_non_mapping_services_raises_catalog_error(tmp_path: Path) -> None:
with pytest.raises(CatalogError, match="'services' must be a mapping"):
load_catalog(_write(tmp_path, "services:\n - redis\n"))
def test_non_mapping_entry_raises_catalog_error_naming_the_key(tmp_path: Path) -> None:
with pytest.raises(CatalogError) as excinfo:
load_catalog(_write(tmp_path, "services:\n redis: just-a-string\n"))
assert excinfo.value.key == "redis"
assert "must be a mapping" in str(excinfo.value)
def test_non_string_field_key_raises_catalog_error_naming_the_key(tmp_path: Path) -> None:
"""A non-string YAML key inside an entry breaks `**body`; it must surface as CatalogError."""
body = textwrap.dedent("""
services:
redis:
1: oops
chart: bitnamilegacy/redis
chart_version: 20.6.2
sizes: {}
""")
with pytest.raises(CatalogError) as excinfo:
load_catalog(_write(tmp_path, body))
assert excinfo.value.key == "redis"
def test_bare_mapping_without_services_key_is_accepted(tmp_path: Path) -> None:
"""The top-level `services:` wrapper is optional; a bare service_type mapping also loads."""
body = textwrap.dedent("""
redis:
chart: bitnamilegacy/redis
chart_version: 20.6.2
sizes:
small:
replicas: 1
resources: {}
""")
catalog = load_catalog(_write(tmp_path, body))
assert set(catalog) == {"redis"}
def test_repo_catalog_yaml_is_valid() -> None:
catalog = load_catalog(Path(__file__).parents[2] / "catalog.yaml")
assert set(catalog) == {"elasticsearch", "redis", "postgres", "podinfo", "nginx"}
for entry in catalog.values():
assert set(entry.sizes) == {"small", "medium"}