test_kestra_local_contract.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import json
  2. import subprocess
  3. from pathlib import Path
  4. ROOT = Path(__file__).resolve().parents[1]
  5. COMPOSE = ROOT / "deploy/docker/docker-compose.yml"
  6. KESTRA_CONFIG = ROOT / "deploy/docker/kestra/application.yml"
  7. POSTGRES_BOOTSTRAP = ROOT / "deploy/docker/postgres/init/000-init.sql"
  8. def _compose_config():
  9. result = subprocess.run(
  10. [
  11. "docker",
  12. "compose",
  13. "-f",
  14. str(COMPOSE),
  15. "--profile",
  16. "kestra-mcp",
  17. "config",
  18. "--format",
  19. "json",
  20. ],
  21. cwd=ROOT,
  22. check=True,
  23. capture_output=True,
  24. text=True,
  25. )
  26. return json.loads(result.stdout)
  27. def test_kestra_and_mcp_images_are_immutable_and_network_is_isolated():
  28. config = _compose_config()
  29. kestra = config["services"]["kestra"]
  30. mcp = config["services"]["kestra-mcp"]
  31. assert kestra["image"] == "kestra/kestra:v1.3.20"
  32. assert "--worker-thread=16" in kestra["command"]
  33. assert mcp["image"] == (
  34. "ghcr.io/kestra-io/mcp-server-python@"
  35. "sha256:1fd62028f60914af31e244ddb0086ed5017420313e2c8547ea9c01b8d24dfc1d"
  36. )
  37. assert kestra["ports"] == [
  38. {
  39. "mode": "ingress",
  40. "target": 8080,
  41. "published": "18080",
  42. "host_ip": "127.0.0.1",
  43. "protocol": "tcp",
  44. }
  45. ]
  46. assert "ports" not in mcp
  47. assert set(kestra["networks"]) == {"dataops-test-net"}
  48. assert set(mcp["networks"]) == {"dataops-test-net"}
  49. assert mcp["profiles"] == ["kestra-mcp"]
  50. assert mcp["stdin_open"] is True
  51. def test_kestra_uses_its_own_database_and_mcp_has_a_restricted_tool_surface():
  52. config = _compose_config()
  53. kestra_environment = config["services"]["kestra"]["environment"]
  54. mcp_environment = config["services"]["kestra-mcp"]["environment"]
  55. assert kestra_environment["KESTRA_DB_NAME"] == "kestra"
  56. assert kestra_environment["KESTRA_DB_HOST"] == "postgres"
  57. assert mcp_environment["KESTRA_BASE_URL"] == "http://kestra:8080/api/v1"
  58. assert mcp_environment["KESTRA_TENANT_ID"] == "main"
  59. assert mcp_environment["KESTRA_MCP_LOG_LEVEL"] == "ERROR"
  60. assert set(mcp_environment["KESTRA_MCP_DISABLED_TOOLS"].split(",")) == {
  61. "ee",
  62. "files",
  63. "kv",
  64. "namespace",
  65. }
  66. combined = json.dumps(
  67. {
  68. "kestra": config["services"]["kestra"],
  69. "kestra-mcp": config["services"]["kestra-mcp"],
  70. },
  71. sort_keys=True,
  72. )
  73. for forbidden in (
  74. "source-postgres",
  75. "source-mysql",
  76. "source-test-password",
  77. "source-root-test-password",
  78. "DATASOURCE_CREDENTIAL_MASTER_KEY",
  79. ):
  80. assert forbidden not in combined
  81. def test_kestra_configuration_and_fresh_database_bootstrap_are_present():
  82. assert KESTRA_CONFIG.is_file()
  83. source = KESTRA_CONFIG.read_text(encoding="utf-8")
  84. assert "datasources:" in source
  85. assert "postgres:" in source
  86. assert "url: jdbc:postgresql://${KESTRA_DB_HOST}:5432/${KESTRA_DB_NAME}" in source
  87. assert "basic-auth:" in source
  88. assert "${KESTRA_USERNAME}" in source
  89. assert "${KESTRA_PASSWORD}" in source
  90. assert "type: local" in source
  91. assert "tutorial-flows:" in source
  92. assert "enabled: false" in source
  93. bootstrap = POSTGRES_BOOTSTRAP.read_text(encoding="utf-8")
  94. assert "CREATE DATABASE kestra" in bootstrap
  95. def test_kestra_mcp_is_documented_as_stdio_not_an_http_endpoint():
  96. readme = (ROOT / "deploy/docker/README.md").read_text(encoding="utf-8")
  97. assert "http://127.0.0.1:18080" in readme
  98. assert "Kestra MCP" in readme
  99. assert "stdio" in readme
  100. assert "docker compose --profile kestra-mcp run --rm -T kestra-mcp" in readme