Files
multica/server/internal/daemon/agents_probe_qoder_test.go
Eve 23923b4c3b fix(daemon): discover qodercli via the login shell (MUL-5524)
Qoder is a fully supported provider, but a GUI-launched daemon could never
detect it. Two gaps, both in agent discovery:

- probeAgentCLIs called resolveAgentExecutablePath directly for qoder instead
  of going through the shared probe() helper, so qoder was the only provider
  with no login-shell fallback. A daemon started from Finder/Launchpad (the
  apple.dmg desktop build) does not inherit the interactive shell PATH, so a
  qodercli in an npm global prefix or any ~/.zshrc-added dir stayed invisible
  no matter how often the daemon restarted.
- "qodercli" was missing from defaultAgentCommandNames, which is the only list
  cachedShellResolvedAgents asks the login shell about. Even with the fallback
  wired up, the resolver would not have looked for it.

TestDefaultAgentCommandNamesCoversAllProbes was supposed to catch exactly this,
but it parsed config.go for probe() calls and silently became a no-op when
probeAgentCLIs moved to agents_probe.go. It now parses agents_probe.go and
asserts it found at least one probe() per default command, so a future move
fails loudly instead of passing vacuously.

Pinned-path semantics are unchanged: an absolute/relative MULTICA_QODER_PATH
that does not exist stays a hard miss rather than silently resolving a
different binary.

Co-authored-by: multica-agent <github@multica.ai>
2026-07-30 14:29:13 +08:00

76 lines
3.1 KiB
Go

package daemon
import (
"testing"
)
// TestProbeAgentCLIs_QoderResolvesViaLoginShell locks down the fix for the
// Qoder discovery gap reported from the desktop app (MUL-5524).
//
// Every other provider is probed through the shared probe() helper, which falls
// back to the user's login shell when the daemon's own PATH can't see a bare
// command name. Qoder was probed with a bare resolveAgentExecutablePath call
// and therefore had no fallback at all: a GUI/Launchpad-started daemon (the
// apple.dmg desktop build) does not inherit the interactive shell PATH, so a
// `qodercli` living in an npm global prefix or any ~/.zshrc-added dir was
// undetectable no matter how many times the daemon restarted.
func TestProbeAgentCLIs_QoderResolvesViaLoginShell(t *testing.T) {
orig := resolveAgentsViaLoginShell
t.Cleanup(func() { resolveAgentsViaLoginShell = orig })
resolveAgentsViaLoginShell = func([]string) map[string]string {
return map[string]string{"qodercli": "/fake/npm-global/bin/qodercli"}
}
resetShellResolveCacheForTest(t)
// An empty PATH guarantees the exec.LookPath leg misses, so the only way
// qoder can resolve is the login-shell fallback.
t.Setenv("PATH", "")
agents := probeAgentCLIs()
entry, ok := agents["qoder"]
if !ok {
t.Fatal("qoder was not discovered via the login-shell fallback; " +
"a GUI-launched daemon cannot see a qodercli installed on the interactive shell PATH")
}
if entry.Path != "/fake/npm-global/bin/qodercli" {
t.Errorf("qoder path = %q, want /fake/npm-global/bin/qodercli", entry.Path)
}
if entry.Command != "qodercli" {
t.Errorf("qoder command = %q, want qodercli", entry.Command)
}
}
// TestProbeAgentCLIs_QoderPinnedPathStaysHardMiss keeps the fallback from
// rescuing an operator-pinned MULTICA_QODER_PATH. A pinned absolute path that
// no longer exists must stay a miss rather than silently resolve a different
// binary — the same rule probe() applies to every other provider.
func TestProbeAgentCLIs_QoderPinnedPathStaysHardMiss(t *testing.T) {
orig := resolveAgentsViaLoginShell
t.Cleanup(func() { resolveAgentsViaLoginShell = orig })
resolveAgentsViaLoginShell = func([]string) map[string]string {
return map[string]string{"qodercli": "/fake/npm-global/bin/qodercli"}
}
resetShellResolveCacheForTest(t)
t.Setenv("PATH", "")
t.Setenv("MULTICA_QODER_PATH", "/nonexistent/pinned/qodercli")
if entry, ok := probeAgentCLIs()["qoder"]; ok {
t.Errorf("pinned-but-missing MULTICA_QODER_PATH resolved to %q, want a hard miss", entry.Path)
}
}
// TestDefaultAgentCommandNamesIncludesQoder guards the other half of the same
// bug: cachedShellResolvedAgents only asks the login shell about the names in
// defaultAgentCommandNames, so omitting "qodercli" would leave the fallback
// permanently blind to Qoder even once the probe consults it.
func TestDefaultAgentCommandNamesIncludesQoder(t *testing.T) {
for _, name := range defaultAgentCommandNames {
if name == "qodercli" {
return
}
}
t.Fatal(`defaultAgentCommandNames is missing "qodercli"; the login-shell resolver ` +
"only pre-fetches names in that list, so Qoder stays undetectable on a GUI-launched daemon")
}