"""Tests for instance_config loading."""
import pytest
class TestInstanceConfig:
def test_missing_config_returns_defaults(self, tmp_path, monkeypatch):
monkeypatch.setenv("DATA_DIR", str(tmp_path))
monkeypatch.setenv("TESTING", "1")
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-minimum-32-characters!!")
from app.instance_config import get_instance_name
name = get_instance_name()
assert isinstance(name, str)
def test_reads_nested_instance_name(self, tmp_path, monkeypatch):
"""get_instance_name should read instance.name from YAML, not flat instance_name."""
monkeypatch.setenv("DATA_DIR", str(tmp_path))
monkeypatch.setenv("TESTING", "1")
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-minimum-32-characters!!")
state_dir = tmp_path / "state"
state_dir.mkdir(exist_ok=True)
(state_dir / "instance.yaml").write_text(
"instance:\n name: Acme Analytics\n subtitle: Data Team\n"
)
import importlib
import app.instance_config as mod
# Reset cached config to force reload
mod._instance_config = None
importlib.reload(mod)
assert mod.get_instance_name() == "Acme Analytics"
assert mod.get_instance_subtitle() == "Data Team"
# Cleanup: reset cache after test
mod._instance_config = None
class TestInstanceBrand:
"""Brand and workspace_dir resolution: env > YAML > default,
workspace_dir derives from brand when not explicitly set."""
def _reload(self, tmp_path, monkeypatch):
monkeypatch.setenv("DATA_DIR", str(tmp_path))
monkeypatch.setenv("TESTING", "1")
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-minimum-32-characters!!")
import importlib
import app.instance_config as mod
mod._instance_config = None
importlib.reload(mod)
return mod
def test_brand_defaults_to_agnes(self, tmp_path, monkeypatch):
monkeypatch.delenv("AGNES_INSTANCE_BRAND", raising=False)
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_instance_brand() == "Agnes"
mod._instance_config = None
def test_brand_from_yaml(self, tmp_path, monkeypatch):
monkeypatch.delenv("AGNES_INSTANCE_BRAND", raising=False)
state_dir = tmp_path / "state"
state_dir.mkdir(exist_ok=True)
(state_dir / "instance.yaml").write_text(
"instance:\n name: Acme\n brand: Foundry AI\n"
)
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_instance_brand() == "Foundry AI"
mod._instance_config = None
def test_brand_env_overrides_yaml(self, tmp_path, monkeypatch):
state_dir = tmp_path / "state"
state_dir.mkdir(exist_ok=True)
(state_dir / "instance.yaml").write_text(
"instance:\n name: Acme\n brand: FromYaml\n"
)
monkeypatch.setenv("AGNES_INSTANCE_BRAND", "FromEnv")
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_instance_brand() == "FromEnv"
mod._instance_config = None
def test_brand_empty_falls_back_to_default(self, tmp_path, monkeypatch):
# Empty env should not override the YAML/default to empty.
monkeypatch.setenv("AGNES_INSTANCE_BRAND", " ")
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_instance_brand() == "Agnes"
mod._instance_config = None
def test_workspace_dir_derives_from_brand(self, tmp_path, monkeypatch):
monkeypatch.delenv("AGNES_WORKSPACE_DIR_NAME", raising=False)
monkeypatch.setenv("AGNES_INSTANCE_BRAND", "Foundry AI")
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_workspace_dir_name() == "FoundryAI"
mod._instance_config = None
def test_workspace_dir_strips_all_non_alphanumeric(self, tmp_path, monkeypatch):
monkeypatch.delenv("AGNES_WORKSPACE_DIR_NAME", raising=False)
monkeypatch.setenv("AGNES_INSTANCE_BRAND", "ACME's Data!")
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_workspace_dir_name() == "ACMEsData"
mod._instance_config = None
def test_workspace_dir_default_when_brand_unset(self, tmp_path, monkeypatch):
monkeypatch.delenv("AGNES_WORKSPACE_DIR_NAME", raising=False)
monkeypatch.delenv("AGNES_INSTANCE_BRAND", raising=False)
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_workspace_dir_name() == "Agnes"
mod._instance_config = None
def test_workspace_dir_explicit_env_overrides_derivation(self, tmp_path, monkeypatch):
monkeypatch.setenv("AGNES_INSTANCE_BRAND", "Foundry AI")
monkeypatch.setenv("AGNES_WORKSPACE_DIR_NAME", "fdry")
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_workspace_dir_name() == "fdry"
mod._instance_config = None
def test_workspace_dir_explicit_yaml_overrides_derivation(self, tmp_path, monkeypatch):
monkeypatch.delenv("AGNES_WORKSPACE_DIR_NAME", raising=False)
monkeypatch.delenv("AGNES_INSTANCE_BRAND", raising=False)
state_dir = tmp_path / "state"
state_dir.mkdir(exist_ok=True)
(state_dir / "instance.yaml").write_text(
"instance:\n name: Acme\n brand: Foundry AI\n workspace_dir: fdry\n"
)
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_workspace_dir_name() == "fdry"
mod._instance_config = None
def test_brand_flows_into_resolve_lines(self, tmp_path, monkeypatch):
"""Brand + workspace_dir substitute into the setup script lines."""
mod = self._reload(tmp_path, monkeypatch)
from app.web.setup_instructions import resolve_lines
joined = "\n".join(resolve_lines(
"agnes.whl",
instance_brand="Foundry AI",
workspace_dir="FoundryAI",
))
assert "Set up the Foundry AI CLI on this machine." in joined
# Step 2 is a pwd-check now (no auto-mkdir); brand + workspace_dir
# thread through the warning copy + expected-path string.
assert "$HOME/Desktop/FoundryAI" in joined
assert "mkdir -p ~/Desktop/FoundryAI && cd ~/Desktop/FoundryAI" in joined
assert "Bootstrap your Foundry AI workspace" in joined
assert "Foundry AI workspace is ready" in joined
# No raw placeholders survive substitution.
assert "{instance_brand}" not in joined
assert "{workspace_dir}" not in joined
mod._instance_config = None
def test_default_brand_keeps_agnes_branding(self, tmp_path, monkeypatch):
"""Backwards-compat: callers that don't pass brand/workspace_dir
get the literal 'Agnes' / '~/Desktop/Agnes' rendering."""
mod = self._reload(tmp_path, monkeypatch)
from app.web.setup_instructions import resolve_lines
joined = "\n".join(resolve_lines("agnes.whl"))
assert "Set up the Agnes CLI on this machine." in joined
# Step 2 is a pwd-check (no auto-mkdir); default path threads
# through as `$HOME/Desktop/Agnes` + the warning's manual-mkdir example.
assert "$HOME/Desktop/Agnes" in joined
assert "mkdir -p ~/Desktop/Agnes && cd ~/Desktop/Agnes" in joined
assert "Bootstrap your Agnes workspace" in joined
assert "Agnes workspace is ready" in joined
mod._instance_config = None
class TestCustomScripts:
"""instance.custom_scripts — operator-injected HTML/JS blocks rendered
by base.html. Validates the normalization + filtering done by
get_custom_scripts() so the template can iterate over a clean list."""
def _reload(self, tmp_path, monkeypatch):
monkeypatch.setenv("DATA_DIR", str(tmp_path))
monkeypatch.setenv("TESTING", "1")
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-minimum-32-characters!!")
import importlib
import app.instance_config as mod
mod._instance_config = None
importlib.reload(mod)
return mod
def _write(self, tmp_path, yaml_body: str):
state_dir = tmp_path / "state"
state_dir.mkdir(exist_ok=True)
(state_dir / "instance.yaml").write_text(yaml_body)
def test_yaml_absent_returns_empty_list(self, tmp_path, monkeypatch):
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_custom_scripts() == []
mod._instance_config = None
def test_valid_entry_normalized(self, tmp_path, monkeypatch):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
" - name: marker-io\n"
" enabled: true\n"
" placement: head_end\n"
" html: |\n"
" \n"
))
mod = self._reload(tmp_path, monkeypatch)
scripts = mod.get_custom_scripts()
assert len(scripts) == 1
s = scripts[0]
assert s["name"] == "marker-io"
assert s["enabled"] is True
assert s["placement"] == "head_end"
assert "markerConfig" in s["html"]
mod._instance_config = None
def test_disabled_entry_dropped(self, tmp_path, monkeypatch):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
" - name: off\n"
" enabled: false\n"
" placement: head_end\n"
" html: \n"
))
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_custom_scripts() == []
mod._instance_config = None
def test_empty_html_dropped(self, tmp_path, monkeypatch):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
" - name: noop\n"
" enabled: true\n"
" placement: head_end\n"
" html: ' '\n"
))
mod = self._reload(tmp_path, monkeypatch)
assert mod.get_custom_scripts() == []
mod._instance_config = None
def test_bad_placement_dropped_with_warning(self, tmp_path, monkeypatch, caplog):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
" - name: typo\n"
" enabled: true\n"
" placement: body_start\n"
" html: \n"
))
mod = self._reload(tmp_path, monkeypatch)
import logging
with caplog.at_level(logging.WARNING, logger="app.instance_config"):
assert mod.get_custom_scripts() == []
assert any("unknown placement" in r.message for r in caplog.records)
mod._instance_config = None
def test_missing_placement_defaults_to_head_end(self, tmp_path, monkeypatch):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
" - name: defaulting\n"
" enabled: true\n"
" html: \n"
))
mod = self._reload(tmp_path, monkeypatch)
scripts = mod.get_custom_scripts()
assert len(scripts) == 1
assert scripts[0]["placement"] == "head_end"
mod._instance_config = None
def test_three_placements_all_pass_through(self, tmp_path, monkeypatch):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
" - {name: a, enabled: true, placement: head_start, html: ''}\n"
" - {name: b, enabled: true, placement: head_end, html: ''}\n"
" - {name: c, enabled: true, placement: body_end, html: ''}\n"
))
mod = self._reload(tmp_path, monkeypatch)
scripts = mod.get_custom_scripts()
assert [s["placement"] for s in scripts] == ["head_start", "head_end", "body_end"]
assert [s["name"] for s in scripts] == ["a", "b", "c"]
mod._instance_config = None
def test_non_list_value_ignored_with_warning(self, tmp_path, monkeypatch, caplog):
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts: not-a-list\n"
))
mod = self._reload(tmp_path, monkeypatch)
import logging
with caplog.at_level(logging.WARNING, logger="app.instance_config"):
assert mod.get_custom_scripts() == []
assert any("must be a list" in r.message for r in caplog.records)
mod._instance_config = None
@pytest.mark.parametrize("enabled_yaml,expect_dropped", [
# Boolean false in every YAML truthy-shape the operator might use.
# All of these must drop the entry so the kill switch behaves the
# same regardless of whether the operator pasted a quoted block.
("false", True),
("False", True),
('"false"', True), # quoted string — bool("false") == True in Python
('"no"', True),
('"NO"', True),
('"off"', True),
('"0"', True),
("0", True),
# Boolean true / typical live values must keep the entry alive.
("true", False),
("True", False),
('"true"', False),
('"yes"', False),
("1", False),
])
def test_enabled_coercion(self, tmp_path, monkeypatch, enabled_yaml, expect_dropped):
"""Quoted-string + numeric `enabled` values must be coerced the same
way the operator expects from a Boolean field — the kill switch is
the whole point of the field, and `bool("false") == True` would
silently leave the snippet live (review PR #372)."""
self._write(tmp_path, (
"instance:\n"
" name: Acme\n"
" custom_scripts:\n"
f" - name: probe\n"
f" enabled: {enabled_yaml}\n"
" placement: head_end\n"
" html: \n"
))
mod = self._reload(tmp_path, monkeypatch)
scripts = mod.get_custom_scripts()
if expect_dropped:
assert scripts == [], (
f"enabled={enabled_yaml!r} should drop the entry but it survived"
)
else:
assert len(scripts) == 1, (
f"enabled={enabled_yaml!r} should keep the entry but it was dropped"
)
mod._instance_config = None