diff --git a/Dockerfile b/Dockerfile index 1fccb212..635dfdaa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN --mount=type=bind,from=wheels,source=/wheels,target=/wheels,ro \ COPY --from=wheels /usr/local/bin/kubectl /usr/local/bin/kubectl COPY blueprints/ /app/blueprints/ COPY specializations/ /app/specializations/ +COPY crews/ /app/crews/ WORKDIR /app USER 10001:10001 diff --git a/tests/unit/test_image_ships_runtime_catalogs.py b/tests/unit/test_image_ships_runtime_catalogs.py new file mode 100644 index 00000000..57c5bc18 --- /dev/null +++ b/tests/unit/test_image_ships_runtime_catalogs.py @@ -0,0 +1,40 @@ +"""The ALM image must ship every catalog directory the runtime loads by name. + +`Settings` addresses blueprints, specializations and crews as bare relative +paths resolved against the container's WORKDIR. A directory that is not COPYd +into the image therefore loads as *empty* rather than failing — crews shipped +this way for a while, and every dynamic crew pick silently resolved to nothing. +""" + +from pathlib import Path + +from devai.config import Settings + +DOCKERFILE = Path("Dockerfile") +WORKDIR = "/app" + +# config field → the repo directory it names +_CATALOGS = { + "pipeline_blueprint_dir": "blueprints", + "specializations_dir": "specializations", + "crews_dir": "crews", +} + + +def test_every_catalog_default_is_a_relative_directory_that_exists() -> None: + settings = Settings() + + for field, directory in _CATALOGS.items(): + value = getattr(settings, field) + assert value == directory, f"{field} default drifted from {directory!r}" + assert Path(directory).is_dir(), f"{directory}/ is missing from the repo" + + +def test_the_image_copies_every_catalog_into_the_workdir() -> None: + source = DOCKERFILE.read_text() + + assert f"WORKDIR {WORKDIR}" in source + for directory in _CATALOGS.values(): + assert f"COPY {directory}/ {WORKDIR}/{directory}/" in source, ( + f"{directory}/ is never copied into the image, so it loads empty at runtime" + )