@@ -796,6 +796,162 @@ func TestPostSetup_InvalidOptionalFloat_Ignores(t *testing.T) {
796796 }
797797}
798798
799+ // ---------- POST /api/service/restart & /api/service/stop -------------------
800+
801+ func TestServiceRestart_WithCallback_Returns200 (t * testing.T ) {
802+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
803+ called := make (chan struct {}, 1 )
804+ h .WithServiceControl (func () { called <- struct {}{} }, nil )
805+
806+ w := serve (t , h , http .MethodPost , "/api/service/restart" , "" )
807+ if w .Code != http .StatusOK {
808+ t .Fatalf ("POST /api/service/restart: want 200, got %d — body: %s" , w .Code , w .Body .String ())
809+ }
810+ var resp struct {
811+ OK bool `json:"ok"`
812+ Message string `json:"message"`
813+ }
814+ if err := json .NewDecoder (w .Body ).Decode (& resp ); err != nil {
815+ t .Fatalf ("POST /api/service/restart: invalid JSON: %v" , err )
816+ }
817+ if ! resp .OK {
818+ t .Errorf ("POST /api/service/restart: want ok=true, got false; message: %s" , resp .Message )
819+ }
820+ if resp .Message == "" {
821+ t .Error ("POST /api/service/restart: want non-empty message" )
822+ }
823+ }
824+
825+ func TestServiceStop_WithCallback_Returns200 (t * testing.T ) {
826+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
827+ called := make (chan struct {}, 1 )
828+ h .WithServiceControl (nil , func () { called <- struct {}{} })
829+
830+ w := serve (t , h , http .MethodPost , "/api/service/stop" , "" )
831+ if w .Code != http .StatusOK {
832+ t .Fatalf ("POST /api/service/stop: want 200, got %d — body: %s" , w .Code , w .Body .String ())
833+ }
834+ var resp struct {
835+ OK bool `json:"ok"`
836+ Message string `json:"message"`
837+ }
838+ if err := json .NewDecoder (w .Body ).Decode (& resp ); err != nil {
839+ t .Fatalf ("POST /api/service/stop: invalid JSON: %v" , err )
840+ }
841+ if ! resp .OK {
842+ t .Errorf ("POST /api/service/stop: want ok=true, got false; message: %s" , resp .Message )
843+ }
844+ if resp .Message == "" {
845+ t .Error ("POST /api/service/stop: want non-empty message" )
846+ }
847+ }
848+
849+ func TestServiceRestart_WithoutCallback_Returns501 (t * testing.T ) {
850+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
851+ // no WithServiceControl call
852+
853+ w := serve (t , h , http .MethodPost , "/api/service/restart" , "" )
854+ if w .Code != http .StatusNotImplemented {
855+ t .Errorf ("POST /api/service/restart without callback: want 501, got %d" , w .Code )
856+ }
857+ var resp struct {
858+ OK bool `json:"ok"`
859+ }
860+ json .NewDecoder (w .Body ).Decode (& resp ) //nolint:errcheck
861+ if resp .OK {
862+ t .Error ("POST /api/service/restart without callback: want ok=false" )
863+ }
864+ }
865+
866+ func TestServiceStop_WithoutCallback_Returns501 (t * testing.T ) {
867+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
868+ // no WithServiceControl call
869+
870+ w := serve (t , h , http .MethodPost , "/api/service/stop" , "" )
871+ if w .Code != http .StatusNotImplemented {
872+ t .Errorf ("POST /api/service/stop without callback: want 501, got %d" , w .Code )
873+ }
874+ var resp struct {
875+ OK bool `json:"ok"`
876+ }
877+ json .NewDecoder (w .Body ).Decode (& resp ) //nolint:errcheck
878+ if resp .OK {
879+ t .Error ("POST /api/service/stop without callback: want ok=false" )
880+ }
881+ }
882+
883+ func TestServiceRestart_GET_DoesNotTriggerAction (t * testing.T ) {
884+ // GET /api/service/restart falls through to the Dashboard catch-all ("GET /")
885+ // and never calls the service callback. This is the intended safety behaviour:
886+ // service actions require an explicit POST.
887+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
888+ triggered := false
889+ h .WithServiceControl (func () { triggered = true }, nil )
890+
891+ w := serve (t , h , http .MethodGet , "/api/service/restart" , "" )
892+ if w .Code != http .StatusOK {
893+ t .Errorf ("GET /api/service/restart: want 200 (dashboard fallback), got %d" , w .Code )
894+ }
895+ if triggered {
896+ t .Error ("GET /api/service/restart: must not trigger restart callback" )
897+ }
898+ // Must not return service-control JSON
899+ if strings .Contains (w .Body .String (), `"ok"` ) {
900+ t .Error ("GET /api/service/restart: must not return service-control JSON" )
901+ }
902+ }
903+
904+ func TestServiceStop_GET_DoesNotTriggerAction (t * testing.T ) {
905+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
906+ triggered := false
907+ h .WithServiceControl (nil , func () { triggered = true })
908+
909+ w := serve (t , h , http .MethodGet , "/api/service/stop" , "" )
910+ if w .Code != http .StatusOK {
911+ t .Errorf ("GET /api/service/stop: want 200 (dashboard fallback), got %d" , w .Code )
912+ }
913+ if triggered {
914+ t .Error ("GET /api/service/stop: must not trigger stop callback" )
915+ }
916+ if strings .Contains (w .Body .String (), `"ok"` ) {
917+ t .Error ("GET /api/service/stop: must not return service-control JSON" )
918+ }
919+ }
920+
921+ func TestDashboard_ServiceControlsVisible_WhenWired (t * testing.T ) {
922+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
923+ h .WithServiceControl (func () {}, func () {})
924+
925+ w := serve (t , h , http .MethodGet , "/" , "" )
926+ if w .Code != http .StatusOK {
927+ t .Fatalf ("dashboard: want 200, got %d" , w .Code )
928+ }
929+ body := w .Body .String ()
930+ if ! strings .Contains (body , "service-controls" ) {
931+ t .Error ("dashboard: expected service-controls section when callbacks wired" )
932+ }
933+ if ! strings .Contains (body , "Restart service" ) {
934+ t .Error ("dashboard: expected 'Restart service' button" )
935+ }
936+ if ! strings .Contains (body , "Stop service" ) {
937+ t .Error ("dashboard: expected 'Stop service' button" )
938+ }
939+ }
940+
941+ func TestDashboard_ServiceControlsHidden_WhenNotWired (t * testing.T ) {
942+ h , _ , _ := newTestWebHandler (t , true , safeEv ())
943+ // no WithServiceControl
944+
945+ w := serve (t , h , http .MethodGet , "/" , "" )
946+ if w .Code != http .StatusOK {
947+ t .Fatalf ("dashboard: want 200, got %d" , w .Code )
948+ }
949+ body := w .Body .String ()
950+ if strings .Contains (body , "service-controls" ) {
951+ t .Error ("dashboard: service-controls section should be absent when not wired" )
952+ }
953+ }
954+
799955func TestGetSetup_DisplaysOptionalFloats (t * testing.T ) {
800956 h , cfgHolder , _ := newTestWebHandler (t , true , safeEv ())
801957
0 commit comments