11package external
22
33import (
4+ "errors"
45 "log"
6+ "os"
7+ "path/filepath"
8+ "strings"
59 "testing"
10+
11+ goplugin "github.com/GoCodeAlone/go-plugin"
612)
713
814func TestExternalPluginManagerStoresCallbackServer (t * testing.T ) {
@@ -15,3 +21,203 @@ func TestExternalPluginManagerStoresCallbackServer(t *testing.T) {
1521 t .Fatal ("expected manager to retain callback server for plugin load" )
1622 }
1723}
24+
25+ func TestExternalPluginManagerReloadFailureKeepsExistingPluginLoaded (t * testing.T ) {
26+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
27+ oldClient := & goplugin.Client {}
28+ manager .clients ["safe-plugin" ] = oldClient
29+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
30+ return nil , errors .New ("candidate handshake failed" )
31+ }
32+
33+ if _ , err := manager .ReloadPlugin ("safe-plugin" ); err == nil {
34+ t .Fatal ("expected reload failure" )
35+ }
36+
37+ got := manager .clients ["safe-plugin" ]
38+ if got != oldClient {
39+ t .Fatal ("reload failure replaced or removed the active plugin client" )
40+ }
41+ if ! manager .IsLoaded ("safe-plugin" ) {
42+ t .Fatal ("reload failure should leave active plugin loaded" )
43+ }
44+ }
45+
46+ func TestExternalPluginManagerLoadPluginStoresCandidateAfterValidation (t * testing.T ) {
47+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
48+ candidate := & goplugin.Client {}
49+ adapter := & ExternalPluginAdapter {}
50+ manager .startPlugin = func (name string ) (* pluginLaunch , error ) {
51+ if name != "safe-plugin" {
52+ t .Fatalf ("unexpected plugin name %q" , name )
53+ }
54+ return & pluginLaunch {client : candidate , adapter : adapter }, nil
55+ }
56+
57+ got , err := manager .LoadPlugin ("safe-plugin" )
58+ if err != nil {
59+ t .Fatalf ("load: %v" , err )
60+ }
61+
62+ if got != adapter {
63+ t .Fatal ("load did not return candidate adapter" )
64+ }
65+ if manager .clients ["safe-plugin" ] != candidate {
66+ t .Fatal ("load did not register candidate client" )
67+ }
68+ if _ , err := manager .LoadPlugin ("safe-plugin" ); err == nil {
69+ t .Fatal ("duplicate load should fail" )
70+ }
71+ }
72+
73+ func TestExternalPluginManagerLoadPluginRejectsInvalidCandidate (t * testing.T ) {
74+ for name , launch := range map [string ]* pluginLaunch {
75+ "nil-launch" : nil ,
76+ "nil-client" : {adapter : & ExternalPluginAdapter {}},
77+ "nil-adapter" : {client : & goplugin.Client {}},
78+ } {
79+ t .Run (name , func (t * testing.T ) {
80+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
81+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
82+ return launch , nil
83+ }
84+
85+ if _ , err := manager .LoadPlugin ("safe-plugin" ); err == nil {
86+ t .Fatal ("expected invalid candidate error" )
87+ }
88+ if manager .IsLoaded ("safe-plugin" ) {
89+ t .Fatal ("invalid candidate should not be registered" )
90+ }
91+ })
92+ }
93+ }
94+
95+ func TestExternalPluginManagerLoadPluginReturnsStartError (t * testing.T ) {
96+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
97+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
98+ return nil , errors .New ("candidate start failed" )
99+ }
100+
101+ if _ , err := manager .LoadPlugin ("safe-plugin" ); err == nil || ! strings .Contains (err .Error (), "candidate start failed" ) {
102+ t .Fatalf ("expected start error, got %v" , err )
103+ }
104+ if manager .IsLoaded ("safe-plugin" ) {
105+ t .Fatal ("start failure should not register plugin" )
106+ }
107+ }
108+
109+ func TestExternalPluginManagerLoadPluginValidatesDiskCandidateBeforeStart (t * testing.T ) {
110+ pluginsDir := t .TempDir ()
111+ pluginDir := filepath .Join (pluginsDir , "safe-plugin" )
112+ if err := os .MkdirAll (filepath .Join (pluginDir , "safe-plugin" ), 0o755 ); err != nil {
113+ t .Fatalf ("create fake binary directory: %v" , err )
114+ }
115+ manifest := []byte (`{
116+ "name": "safe-plugin",
117+ "version": "1.0.0",
118+ "author": "test",
119+ "description": "test plugin"
120+ }` )
121+ if err := os .WriteFile (filepath .Join (pluginDir , "plugin.json" ), manifest , 0o644 ); err != nil {
122+ t .Fatalf ("write manifest: %v" , err )
123+ }
124+ manager := NewExternalPluginManager (pluginsDir , log .Default ())
125+
126+ if _ , err := manager .LoadPlugin ("safe-plugin" ); err == nil || ! strings .Contains (err .Error (), "binary path is a directory" ) {
127+ t .Fatalf ("expected directory binary validation error, got %v" , err )
128+ }
129+ if manager .IsLoaded ("safe-plugin" ) {
130+ t .Fatal ("disk validation failure should not register plugin" )
131+ }
132+ }
133+
134+ func TestExternalPluginManagerReloadWithoutActivePluginLoadsCandidate (t * testing.T ) {
135+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
136+ candidate := & goplugin.Client {}
137+ adapter := & ExternalPluginAdapter {}
138+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
139+ return & pluginLaunch {client : candidate , adapter : adapter }, nil
140+ }
141+
142+ got , err := manager .ReloadPlugin ("safe-plugin" )
143+ if err != nil {
144+ t .Fatalf ("reload: %v" , err )
145+ }
146+
147+ if got != adapter {
148+ t .Fatal ("reload did not return candidate adapter" )
149+ }
150+ if manager .clients ["safe-plugin" ] != candidate {
151+ t .Fatal ("reload without active plugin did not register candidate client" )
152+ }
153+ }
154+
155+ func TestExternalPluginManagerReloadWithoutActivePluginRejectsInvalidCandidate (t * testing.T ) {
156+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
157+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
158+ return & pluginLaunch {client : & goplugin.Client {}}, nil
159+ }
160+
161+ if _ , err := manager .ReloadPlugin ("safe-plugin" ); err == nil {
162+ t .Fatal ("expected invalid candidate error" )
163+ }
164+ if manager .IsLoaded ("safe-plugin" ) {
165+ t .Fatal ("invalid reload candidate should not be registered" )
166+ }
167+ }
168+
169+ func TestExternalPluginManagerReloadWithoutActivePluginReturnsStartError (t * testing.T ) {
170+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
171+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
172+ return nil , errors .New ("candidate start failed" )
173+ }
174+
175+ if _ , err := manager .ReloadPlugin ("safe-plugin" ); err == nil || ! strings .Contains (err .Error (), "candidate start failed" ) {
176+ t .Fatalf ("expected start error, got %v" , err )
177+ }
178+ if manager .IsLoaded ("safe-plugin" ) {
179+ t .Fatal ("start failure should not register plugin" )
180+ }
181+ }
182+
183+ func TestExternalPluginManagerReloadSuccessSwapsAfterCandidateStarts (t * testing.T ) {
184+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
185+ oldClient := & goplugin.Client {}
186+ newClient := & goplugin.Client {}
187+ adapter := & ExternalPluginAdapter {}
188+ manager .clients ["safe-plugin" ] = oldClient
189+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
190+ if manager .clients ["safe-plugin" ] != oldClient {
191+ t .Fatal ("candidate started after old plugin was removed" )
192+ }
193+ return & pluginLaunch {client : newClient , adapter : adapter }, nil
194+ }
195+
196+ got , err := manager .ReloadPlugin ("safe-plugin" )
197+ if err != nil {
198+ t .Fatalf ("reload: %v" , err )
199+ }
200+
201+ if got != adapter {
202+ t .Fatal ("reload did not return candidate adapter" )
203+ }
204+ if got := manager .clients ["safe-plugin" ]; got != newClient {
205+ t .Fatal ("reload success did not register candidate plugin client" )
206+ }
207+ }
208+
209+ func TestExternalPluginManagerReloadLoadedRejectsInvalidCandidate (t * testing.T ) {
210+ manager := NewExternalPluginManager (t .TempDir (), log .Default ())
211+ oldClient := & goplugin.Client {}
212+ manager .clients ["safe-plugin" ] = oldClient
213+ manager .startPlugin = func (string ) (* pluginLaunch , error ) {
214+ return & pluginLaunch {client : & goplugin.Client {}}, nil
215+ }
216+
217+ if _ , err := manager .ReloadPlugin ("safe-plugin" ); err == nil {
218+ t .Fatal ("expected invalid candidate error" )
219+ }
220+ if manager .clients ["safe-plugin" ] != oldClient {
221+ t .Fatal ("invalid reload candidate replaced active plugin" )
222+ }
223+ }
0 commit comments