@@ -289,3 +289,117 @@ func TestPathsAreClean(t *testing.T) {
289289 }
290290 }
291291}
292+
293+ // writeFakeDaemon creates an executable file at the given path so exec.LookPath
294+ // will accept it as resolvable. Returns the absolute path actually written.
295+ func writeFakeDaemon (t * testing.T , dir string ) string {
296+ t .Helper ()
297+ if err := os .MkdirAll (dir , 0o755 ); err != nil {
298+ t .Fatalf ("failed to mkdir %s: %v" , dir , err )
299+ }
300+ p := filepath .Join (dir , "shelltime-daemon" )
301+ if err := os .WriteFile (p , []byte ("#!/bin/sh\n exit 0\n " ), 0o755 ); err != nil {
302+ t .Fatalf ("failed to write fake daemon: %v" , err )
303+ }
304+ return p
305+ }
306+
307+ // withIsolatedDaemonResolution sets up a hermetic environment for
308+ // ResolveDaemonBinaryPath: a temp $HOME, an empty $PATH (caller can re-add
309+ // dirs), and an empty Homebrew search list so the host machine's installs
310+ // (e.g. real /opt/homebrew/bin/shelltime-daemon) don't leak into assertions.
311+ // Returns the temp home directory.
312+ func withIsolatedDaemonResolution (t * testing.T ) string {
313+ t .Helper ()
314+ home := t .TempDir ()
315+ t .Setenv ("HOME" , home )
316+ t .Setenv ("PATH" , "" )
317+
318+ prev := daemonHomebrewSearchPaths
319+ daemonHomebrewSearchPaths = nil
320+ t .Cleanup (func () { daemonHomebrewSearchPaths = prev })
321+
322+ return home
323+ }
324+
325+ func TestResolveDaemonBinaryPath (t * testing.T ) {
326+ t .Run ("returns curl-installer path when nothing else is on PATH" , func (t * testing.T ) {
327+ home := withIsolatedDaemonResolution (t )
328+
329+ curl := writeFakeDaemon (t , filepath .Join (home , COMMAND_BASE_STORAGE_FOLDER , "bin" ))
330+
331+ got , err := ResolveDaemonBinaryPath ()
332+ if err != nil {
333+ t .Fatalf ("unexpected error: %v" , err )
334+ }
335+ if got != curl {
336+ t .Errorf ("expected %s, got %s" , curl , got )
337+ }
338+ })
339+
340+ t .Run ("prefers PATH-resolved binary over curl-installer path" , func (t * testing.T ) {
341+ home := withIsolatedDaemonResolution (t )
342+
343+ brewDir := t .TempDir ()
344+ brewPath := writeFakeDaemon (t , brewDir )
345+ t .Setenv ("PATH" , brewDir )
346+
347+ // stale curl-installer copy that should be ignored
348+ writeFakeDaemon (t , filepath .Join (home , COMMAND_BASE_STORAGE_FOLDER , "bin" ))
349+
350+ got , err := ResolveDaemonBinaryPath ()
351+ if err != nil {
352+ t .Fatalf ("unexpected error: %v" , err )
353+ }
354+ if got != brewPath {
355+ t .Errorf ("expected PATH-resolved %s, got %s" , brewPath , got )
356+ }
357+ })
358+
359+ t .Run ("uses explicit Homebrew search list when PATH is empty" , func (t * testing.T ) {
360+ home := withIsolatedDaemonResolution (t )
361+
362+ brewDir := t .TempDir ()
363+ brewPath := writeFakeDaemon (t , brewDir )
364+ daemonHomebrewSearchPaths = []string {brewDir }
365+
366+ // stale curl-installer copy that should be ignored
367+ writeFakeDaemon (t , filepath .Join (home , COMMAND_BASE_STORAGE_FOLDER , "bin" ))
368+
369+ got , err := ResolveDaemonBinaryPath ()
370+ if err != nil {
371+ t .Fatalf ("unexpected error: %v" , err )
372+ }
373+ if got != brewPath {
374+ t .Errorf ("expected Homebrew search result %s, got %s" , brewPath , got )
375+ }
376+ })
377+
378+ t .Run ("skips PATH result that points back at the curl-installer path" , func (t * testing.T ) {
379+ home := withIsolatedDaemonResolution (t )
380+
381+ curlBin := filepath .Join (home , COMMAND_BASE_STORAGE_FOLDER , "bin" )
382+ curl := writeFakeDaemon (t , curlBin )
383+ // Put only the curl-installer dir on PATH; LookPath would return curl,
384+ // but the resolver must fall through and still return the curl path
385+ // from step 3 (so the cleanup logic in daemon.install can detect it).
386+ t .Setenv ("PATH" , curlBin )
387+
388+ got , err := ResolveDaemonBinaryPath ()
389+ if err != nil {
390+ t .Fatalf ("unexpected error: %v" , err )
391+ }
392+ if got != curl {
393+ t .Errorf ("expected curl-installer fallback %s, got %s" , curl , got )
394+ }
395+ })
396+
397+ t .Run ("returns error when no daemon is found anywhere" , func (t * testing.T ) {
398+ withIsolatedDaemonResolution (t )
399+
400+ _ , err := ResolveDaemonBinaryPath ()
401+ if err == nil {
402+ t .Fatal ("expected error when no daemon binary exists, got nil" )
403+ }
404+ })
405+ }
0 commit comments