hephaestus/heph.nvim/tests/e2e/managed_daemon_spec.lua
Erich Blume e3db2ac550 heph.nvim: plug-and-play managed daemon (autostart, self-heal, client/server guardrail)
The plugin now manages its own hephd by default (autostart = true): if nothing
is serving the socket it spawns a local daemon against the default XDG paths,
kills only what it spawned on VimLeavePre, and self-heals — rpc.call retries
once through a respawn hook when the connection drops (the prior owner releases
the DB lock on exit, so a respawn can claim it).

- daemon.ensure() connects to an already-running daemon (any mode) or spawns one
  we own; stop_spawned()/is_managed() track lifecycle.
- A server/client daemon you started is always respected (spawn only when nothing
  serves the socket). autostart = false → connect-only, warns/errors if down,
  and clears the self-heal hook so it fails loudly.
- config: autostart defaults true; new `db` option; $HEPH_SOCKET / $HEPH_DB
  fallbacks isolate a dev Neovim onto a separate daemon + DB.

e2e: managed_daemon_spec covers autostart spawn, self-heal-after-kill, and
connect-only error. 10 specs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:37:49 -07:00

68 lines
2.1 KiB
Lua

-- The plugin-managed daemon lifecycle (tech-spec §8): plug-and-play autostart,
-- self-heal on a dropped connection, and connect-only when autostart is off.
local h = require("e2e.helpers")
describe("managed daemon", function()
local t
before_each(function()
t = h.tmp() -- temp paths; no daemon spawned by the harness
end)
after_each(function()
pcall(function()
require("heph.daemon").stop_spawned()
end)
pcall(function()
require("heph.rpc").close()
end)
require("heph.rpc").set_respawn(nil)
t.rm()
end)
it("autostart spawns a local daemon and connects plug-and-play", function()
require("heph").setup({
socket = t.sock,
db = t.db,
bin = h.hephd_bin(),
autostart = true,
keymaps = false,
})
assert.is_true(require("heph.daemon").is_managed())
-- A real call works because the plugin brought the daemon up itself.
assert.is_truthy(require("heph.rpc").call("health", {}))
end)
it("self-heals: respawns and reconnects when the daemon dies", function()
require("heph").setup({
socket = t.sock,
db = t.db,
bin = h.hephd_bin(),
autostart = true,
keymaps = false,
})
require("heph.rpc").call("health", {})
-- Kill the managed daemon out from under the plugin.
local m = require("heph.daemon")._managed
m.handle:kill("sigterm")
vim.wait(2000, function()
return m.exited.done
end, 20)
-- The next call transparently respawns the daemon and succeeds.
assert.is_truthy(require("heph.rpc").call("health", {}))
assert.is_true(require("heph.daemon").is_managed())
end)
it("connect-only (autostart=false) errors when no daemon is running", function()
require("heph").setup({
socket = t.sock,
autostart = false,
keymaps = false,
})
assert.is_false(require("heph.daemon").is_managed())
-- No daemon, no autostart, no self-heal → a call fails loudly.
local ok = pcall(require("heph.rpc").call, "health", {})
assert.is_false(ok, "expected connect-only to fail with no daemon running")
end)
end)