1+ package controlpanel
2+
3+ import (
4+ "reflect"
5+ "testing"
6+ "unsafe"
7+
8+ "github.com/google/uuid"
9+ "github.com/stretchr/testify/assert"
10+
11+ "github.com/langgenius/dify-plugin-daemon/internal/core/debugging_runtime"
12+ "github.com/langgenius/dify-plugin-daemon/internal/core/local_runtime"
13+ "github.com/langgenius/dify-plugin-daemon/internal/types/app"
14+ "github.com/langgenius/dify-plugin-daemon/pkg/entities/manifest_entities"
15+ "github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
16+ )
17+
18+ type mockNotifier struct {
19+ connected bool
20+ disconnected bool
21+ }
22+
23+ func (m * mockNotifier ) OnLocalRuntimeStarting (plugin_entities.PluginUniqueIdentifier ) {}
24+ func (m * mockNotifier ) OnLocalRuntimeReady (* local_runtime.LocalPluginRuntime ) {}
25+ func (m * mockNotifier ) OnLocalRuntimeStartFailed (plugin_entities.PluginUniqueIdentifier , error ) {}
26+ func (m * mockNotifier ) OnLocalRuntimeStop (* local_runtime.LocalPluginRuntime ) {}
27+ func (m * mockNotifier ) OnLocalRuntimeStopped (* local_runtime.LocalPluginRuntime ) {}
28+ func (m * mockNotifier ) OnLocalRuntimeScaleUp (* local_runtime.LocalPluginRuntime , int32 ) {}
29+ func (m * mockNotifier ) OnLocalRuntimeScaleDown (* local_runtime.LocalPluginRuntime , int32 ) {}
30+ func (m * mockNotifier ) OnLocalRuntimeInstanceLog (* local_runtime.LocalPluginRuntime , * local_runtime.PluginInstance , plugin_entities.PluginLogEvent ) {}
31+ func (m * mockNotifier ) OnDebuggingRuntimeConnected (r * debugging_runtime.RemotePluginRuntime ) { m .connected = true }
32+ func (m * mockNotifier ) OnDebuggingRuntimeDisconnected (r * debugging_runtime.RemotePluginRuntime ) { m .disconnected = true }
33+
34+ // setPrivateString sets an unexported string field on a struct value via unsafe reflection.
35+ func setPrivateString (target any , field string , value string ) {
36+ rv := reflect .ValueOf (target ).Elem ()
37+ f := rv .FieldByName (field )
38+ reflect .NewAt (f .Type (), unsafe .Pointer (f .UnsafeAddr ())).Elem ().SetString (value )
39+ }
40+
41+ func newFakeRemoteRuntime (t * testing.T , name , version string ) * debugging_runtime.RemotePluginRuntime {
42+ t .Helper ()
43+
44+ r := & debugging_runtime.RemotePluginRuntime {
45+ PluginRuntime : plugin_entities.PluginRuntime {
46+ Config : plugin_entities.PluginDeclaration {
47+ PluginDeclarationWithoutAdvancedFields : plugin_entities.PluginDeclarationWithoutAdvancedFields {
48+ // Author is overwritten with tenantId inside Identity()
49+ Name : name ,
50+ Version : manifest_entities .Version (version ),
51+ },
52+ },
53+ },
54+ }
55+
56+ // Provide required tenantId and checksum so Identity() succeeds
57+ setPrivateString (r , "tenantId" , uuid .New ().String ())
58+ setPrivateString (r , "checksum" , "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" )
59+
60+ return r
61+ }
62+
63+ func TestOnDebuggingRuntimeConnectedAndDisconnected (t * testing.T ) {
64+ cp := NewControlPanel (& app.Config {PluginLocalLaunchingConcurrent : 1 }, nil , nil , nil , nil )
65+
66+ // Attach mock notifier to observe callbacks
67+ mn := & mockNotifier {}
68+ cp .AddNotifier (mn )
69+
70+ r := newFakeRemoteRuntime (t , "conn_test" , "1.2.3" )
71+
72+ // Connected path
73+ err := cp .onDebuggingRuntimeConnected (r )
74+ assert .NoError (t , err )
75+
76+ id , err := r .Identity ()
77+ assert .NoError (t , err )
78+
79+ // Stored in runtime map and notifier called
80+ _ , ok := cp .debuggingPluginRuntime .Load (id )
81+ assert .True (t , ok , "runtime should be stored on connect" )
82+ assert .True (t , mn .connected , "connected notifier should be triggered" )
83+
84+ // Disconnected path
85+ cp .onDebuggingRuntimeDisconnected (r )
86+
87+ _ , ok = cp .debuggingPluginRuntime .Load (id )
88+ assert .False (t , ok , "runtime should be removed on disconnect" )
89+ assert .True (t , mn .disconnected , "disconnected notifier should be triggered" )
90+ }
0 commit comments