@@ -3,6 +3,7 @@ package plugin
33import (
44 "os"
55 "path/filepath"
6+ "strings"
67 "testing"
78)
89
@@ -400,17 +401,60 @@ func TestParsePluginMD_GitHubSheriff(t *testing.T) {
400401}
401402
402403func TestParsePluginMD_SessionHygiene (t * testing.T ) {
403- // Verify the actual session-hygiene plugin.md parses correctly.
404- content , err := os .ReadFile (filepath .Join (".." , ".." , "plugins" , "session-hygiene" , "plugin.md" ))
404+ // Use a temp dir with a fixture plugin.md and run.sh so the test
405+ // doesn't depend on the local filesystem layout (fails in CI).
406+ pluginDir := t .TempDir ()
407+
408+ pluginContent := []byte (`+++
409+ name = "session-hygiene"
410+ description = "Clean up zombie tmux sessions and orphaned dog sessions"
411+ version = 2
412+
413+ [gate]
414+ type = "cooldown"
415+ duration = "30m"
416+
417+ [tracking]
418+ labels = ["plugin:session-hygiene", "category:cleanup"]
419+ digest = true
420+
421+ [execution]
422+ timeout = "5m"
423+ notify_on_failure = true
424+ severity = "low"
425+ +++
426+
427+ # Session Hygiene
428+
429+ Deterministic cleanup of zombie tmux sessions and orphaned dog sessions.
430+ ` )
431+
432+ if err := os .WriteFile (filepath .Join (pluginDir , "plugin.md" ), pluginContent , 0644 ); err != nil {
433+ t .Fatalf ("writing plugin.md fixture: %v" , err )
434+ }
435+ if err := os .WriteFile (filepath .Join (pluginDir , "run.sh" ), []byte ("#!/bin/bash\n echo ok\n " ), 0755 ); err != nil {
436+ t .Fatalf ("writing run.sh fixture: %v" , err )
437+ }
438+
439+ content , err := os .ReadFile (filepath .Join (pluginDir , "plugin.md" ))
405440 if err != nil {
406- t .Skipf ( "session-hygiene plugin not found (expected in plugins/) : %v" , err )
441+ t .Fatalf ( "reading plugin.md fixture : %v" , err )
407442 }
408443
409- plugin , err := parsePluginMD (content , "/test/session-hygiene" , LocationRig , "gastown" )
444+ plugin , err := parsePluginMD (content , pluginDir , LocationRig , "gastown" )
410445 if err != nil {
411446 t .Fatalf ("parsePluginMD failed: %v" , err )
412447 }
413448
449+ // Verify run.sh detection (loadPlugin does this, not parsePluginMD)
450+ runScriptPath := filepath .Join (pluginDir , "run.sh" )
451+ if info , statErr := os .Stat (runScriptPath ); statErr == nil && ! info .IsDir () {
452+ plugin .HasRunScript = true
453+ }
454+ if ! plugin .HasRunScript {
455+ t .Error ("expected HasRunScript=true for session-hygiene (has run.sh)" )
456+ }
457+
414458 if plugin .Name != "session-hygiene" {
415459 t .Errorf ("expected name 'session-hygiene', got %q" , plugin .Name )
416460 }
@@ -509,3 +553,123 @@ version = 1
509553 t .Errorf ("expected location 'rig', got %q" , plugins [0 ].Location )
510554 }
511555}
556+
557+ func TestLoadPlugin_DetectsRunScript (t * testing.T ) {
558+ tmpDir , err := os .MkdirTemp ("" , "plugin-runsh-test" )
559+ if err != nil {
560+ t .Fatalf ("failed to create temp dir: %v" , err )
561+ }
562+ defer os .RemoveAll (tmpDir )
563+
564+ // Create plugin dir with plugin.md AND run.sh
565+ pluginDir := filepath .Join (tmpDir , "plugins" , "with-script" )
566+ if err := os .MkdirAll (pluginDir , 0755 ); err != nil {
567+ t .Fatalf ("failed to create plugin dir: %v" , err )
568+ }
569+ pluginContent := []byte (`+++
570+ name = "with-script"
571+ description = "Plugin with run.sh"
572+ version = 1
573+ +++
574+
575+ # Instructions (should be ignored when run.sh exists)
576+ ` )
577+ if err := os .WriteFile (filepath .Join (pluginDir , "plugin.md" ), pluginContent , 0644 ); err != nil {
578+ t .Fatalf ("failed to write plugin.md: %v" , err )
579+ }
580+ if err := os .WriteFile (filepath .Join (pluginDir , "run.sh" ), []byte ("#!/bin/bash\n echo hello\n " ), 0755 ); err != nil {
581+ t .Fatalf ("failed to write run.sh: %v" , err )
582+ }
583+
584+ // Create plugin dir with plugin.md only (no run.sh)
585+ pluginDirNoScript := filepath .Join (tmpDir , "plugins" , "no-script" )
586+ if err := os .MkdirAll (pluginDirNoScript , 0755 ); err != nil {
587+ t .Fatalf ("failed to create plugin dir: %v" , err )
588+ }
589+ noScriptContent := []byte (`+++
590+ name = "no-script"
591+ description = "Plugin without run.sh"
592+ version = 1
593+ +++
594+
595+ # Instructions
596+ ` )
597+ if err := os .WriteFile (filepath .Join (pluginDirNoScript , "plugin.md" ), noScriptContent , 0644 ); err != nil {
598+ t .Fatalf ("failed to write plugin.md: %v" , err )
599+ }
600+
601+ scanner := NewScanner (tmpDir , nil )
602+ plugins , err := scanner .DiscoverAll ()
603+ if err != nil {
604+ t .Fatalf ("DiscoverAll failed: %v" , err )
605+ }
606+
607+ if len (plugins ) != 2 {
608+ t .Fatalf ("expected 2 plugins, got %d" , len (plugins ))
609+ }
610+
611+ byName := make (map [string ]* Plugin )
612+ for _ , p := range plugins {
613+ byName [p .Name ] = p
614+ }
615+
616+ if p , ok := byName ["with-script" ]; ! ok {
617+ t .Fatal ("expected to find 'with-script' plugin" )
618+ } else if ! p .HasRunScript {
619+ t .Error ("expected HasRunScript=true for plugin with run.sh" )
620+ }
621+
622+ if p , ok := byName ["no-script" ]; ! ok {
623+ t .Fatal ("expected to find 'no-script' plugin" )
624+ } else if p .HasRunScript {
625+ t .Error ("expected HasRunScript=false for plugin without run.sh" )
626+ }
627+ }
628+
629+ func TestFormatMailBody_WithRunScript (t * testing.T ) {
630+ p := & Plugin {
631+ Name : "test-plugin" ,
632+ Description : "A test plugin" ,
633+ Path : "/home/user/gt/plugins/test-plugin" ,
634+ HasRunScript : true ,
635+ }
636+
637+ body := p .FormatMailBody ()
638+
639+ // Must contain the bash command to run the script
640+ if ! strings .Contains (body , "cd /home/user/gt/plugins/test-plugin && bash run.sh" ) {
641+ t .Error ("expected mail body to contain run.sh execution command" )
642+ }
643+ // Must instruct dog NOT to interpret markdown
644+ if ! strings .Contains (body , "Do NOT interpret the plugin.md instructions" ) {
645+ t .Error ("expected mail body to warn against interpreting markdown" )
646+ }
647+ // Must NOT contain "## Instructions" section
648+ if strings .Contains (body , "## Instructions" ) {
649+ t .Error ("expected mail body to NOT contain markdown instructions section" )
650+ }
651+ }
652+
653+ func TestFormatMailBody_WithoutRunScript (t * testing.T ) {
654+ p := & Plugin {
655+ Name : "test-plugin" ,
656+ Description : "A test plugin" ,
657+ Path : "/home/user/gt/plugins/test-plugin" ,
658+ Instructions : "Do the thing." ,
659+ HasRunScript : false ,
660+ }
661+
662+ body := p .FormatMailBody ()
663+
664+ // Must contain the instructions section
665+ if ! strings .Contains (body , "## Instructions" ) {
666+ t .Error ("expected mail body to contain instructions section" )
667+ }
668+ if ! strings .Contains (body , "Do the thing." ) {
669+ t .Error ("expected mail body to contain plugin instructions" )
670+ }
671+ // Must NOT contain run.sh dispatch
672+ if strings .Contains (body , "bash run.sh" ) {
673+ t .Error ("expected mail body to NOT contain run.sh command" )
674+ }
675+ }
0 commit comments