11package kcp_test
22
33import (
4+ "context"
5+ "os"
6+ "path/filepath"
47 "testing"
8+ "time"
59
610 kcpapis "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
711 kcpcore "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
812 "github.com/stretchr/testify/assert"
13+ "github.com/stretchr/testify/require"
914 "k8s.io/apimachinery/pkg/runtime"
1015 "k8s.io/apimachinery/pkg/types"
1116 "k8s.io/client-go/rest"
1217 ctrl "sigs.k8s.io/controller-runtime"
1318 "sigs.k8s.io/controller-runtime/pkg/metrics/server"
1419
20+ "github.com/platform-mesh/golang-commons/logger"
1521 "github.com/platform-mesh/golang-commons/logger/testlogger"
1622 "github.com/platform-mesh/kubernetes-graphql-gateway/common/config"
1723 "github.com/platform-mesh/kubernetes-graphql-gateway/listener/reconciler"
@@ -156,7 +162,7 @@ func TestKCPReconciler_SetupWithManager(t *testing.T) {
156162 return scheme
157163 }(),
158164 ManagerOpts : ctrl.Options {
159- Metrics : server.Options {BindAddress : "0" }, // Disable metrics for tests
165+ Metrics : server.Options {BindAddress : "0" },
160166 Scheme : func () * runtime.Scheme {
161167 scheme := runtime .NewScheme ()
162168 _ = kcpapis .AddToScheme (scheme )
@@ -171,7 +177,355 @@ func TestKCPReconciler_SetupWithManager(t *testing.T) {
171177 assert .NoError (t , err )
172178 assert .NotNil (t , reconciler )
173179
174- // The SetupWithManager method should work without errors when properly initialized
180+ // Test SetupWithManager with nil manager ( should work based on current implementation)
175181 err = reconciler .SetupWithManager (nil )
176- assert .NoError (t , err )
182+ assert .NoError (t , err , "SetupWithManager should handle nil manager gracefully" )
183+
184+ // Note: Cannot test calling SetupWithManager multiple times because it registers controllers
185+ // and duplicate controller names cause errors. This is expected behavior.
186+ }
187+
188+ func TestKCPReconciler_StartVirtualWorkspaceWatching (t * testing.T ) {
189+ tempDir := t .TempDir ()
190+ log := testlogger .New ().HideLogOutput ().Logger
191+
192+ reconciler , err := kcp .NewKCPReconciler (
193+ config.Config {
194+ OpenApiDefinitionsPath : tempDir ,
195+ },
196+ reconciler.ReconcilerOpts {
197+ Config : & rest.Config {
198+ Host : "https://test.cluster" ,
199+ },
200+ Scheme : func () * runtime.Scheme {
201+ scheme := runtime .NewScheme ()
202+ _ = kcpapis .AddToScheme (scheme )
203+ _ = kcpcore .AddToScheme (scheme )
204+ return scheme
205+ }(),
206+ ManagerOpts : ctrl.Options {
207+ Metrics : server.Options {BindAddress : "0" },
208+ Scheme : func () * runtime.Scheme {
209+ scheme := runtime .NewScheme ()
210+ _ = kcpapis .AddToScheme (scheme )
211+ _ = kcpcore .AddToScheme (scheme )
212+ return scheme
213+ }(),
214+ },
215+ },
216+ log ,
217+ )
218+ require .NoError (t , err )
219+
220+ tests := []struct {
221+ name string
222+ configPath string
223+ setupConfig func (string ) error
224+ expectedError bool
225+ timeout time.Duration
226+ }{
227+ {
228+ name : "empty_config_path_should_return_immediately" ,
229+ configPath : "" ,
230+ setupConfig : func (string ) error { return nil },
231+ expectedError : false ,
232+ timeout : 100 * time .Millisecond ,
233+ },
234+ {
235+ name : "valid_config_file_should_start_watching" ,
236+ configPath : filepath .Join (tempDir , "virtual-ws-config.yaml" ),
237+ setupConfig : func (path string ) error {
238+ content := `
239+ virtualWorkspaces:
240+ - name: "test-workspace"
241+ url: "https://test.cluster"
242+ `
243+ return os .WriteFile (path , []byte (content ), 0644 )
244+ },
245+ expectedError : false ,
246+ timeout : 200 * time .Millisecond ,
247+ },
248+ {
249+ name : "non_existent_config_file_should_handle_gracefully" ,
250+ configPath : filepath .Join (tempDir , "non-existent.yaml" ),
251+ setupConfig : func (string ) error {
252+ return nil // Don't create the file
253+ },
254+ expectedError : true , // May error when trying to watch non-existent directory
255+ timeout : 100 * time .Millisecond ,
256+ },
257+ }
258+
259+ for _ , tt := range tests {
260+ t .Run (tt .name , func (t * testing.T ) {
261+ err := tt .setupConfig (tt .configPath )
262+ require .NoError (t , err )
263+
264+ ctx , cancel := context .WithTimeout (context .Background (), tt .timeout )
265+ defer cancel ()
266+
267+ // Test the actual method
268+ err = reconciler .StartVirtualWorkspaceWatching (ctx , tt .configPath )
269+
270+ if tt .expectedError {
271+ assert .Error (t , err )
272+ } else {
273+ // Should either succeed or be cancelled by context timeout
274+ if err != nil {
275+ // Context cancellation or other expected errors are acceptable
276+ t .Logf ("Got error (possibly expected): %v" , err )
277+ }
278+ }
279+ })
280+ }
281+ }
282+
283+ func TestKCPReconciler_ManagerWrapper_Start (t * testing.T ) {
284+ tempDir := t .TempDir ()
285+ log := testlogger .New ().HideLogOutput ().Logger
286+
287+ reconciler , err := kcp .NewKCPReconciler (
288+ config.Config {
289+ OpenApiDefinitionsPath : tempDir ,
290+ },
291+ reconciler.ReconcilerOpts {
292+ Config : & rest.Config {
293+ Host : "https://test.cluster" ,
294+ },
295+ Scheme : func () * runtime.Scheme {
296+ scheme := runtime .NewScheme ()
297+ _ = kcpapis .AddToScheme (scheme )
298+ _ = kcpcore .AddToScheme (scheme )
299+ return scheme
300+ }(),
301+ ManagerOpts : ctrl.Options {
302+ Metrics : server.Options {BindAddress : "0" },
303+ Scheme : func () * runtime.Scheme {
304+ scheme := runtime .NewScheme ()
305+ _ = kcpapis .AddToScheme (scheme )
306+ _ = kcpcore .AddToScheme (scheme )
307+ return scheme
308+ }(),
309+ },
310+ },
311+ log ,
312+ )
313+ require .NoError (t , err )
314+
315+ // Test that GetManager returns a non-nil manager
316+ manager := reconciler .GetManager ()
317+ assert .NotNil (t , manager )
318+
319+ // Test that the manager's Start method can be called
320+ ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
321+ defer cancel ()
322+
323+ // The Start method should be callable (it will likely fail due to no real cluster setup,
324+ // but it should not panic and should return an error or succeed)
325+ err = manager .Start (ctx )
326+
327+ // We expect either success or failure, but no panic
328+ // The exact behavior depends on the underlying multicluster manager implementation
329+ if err != nil {
330+ // This is expected since we don't have a real cluster setup
331+ t .Logf ("Manager start failed as expected: %v" , err )
332+ } else {
333+ t .Logf ("Manager start succeeded unexpectedly" )
334+ }
335+
336+ // The important thing is that we didn't panic and the method was callable
337+ assert .NotNil (t , manager , "Manager should not be nil" )
338+ }
339+
340+ func TestKCPReconciler_Reconcile_NoOp_Behavior (t * testing.T ) {
341+ tempDir := t .TempDir ()
342+ log := testlogger .New ().HideLogOutput ().Logger
343+
344+ reconciler , err := kcp .NewKCPReconciler (
345+ config.Config {
346+ OpenApiDefinitionsPath : tempDir ,
347+ },
348+ reconciler.ReconcilerOpts {
349+ Config : & rest.Config {
350+ Host : "https://test.cluster" ,
351+ },
352+ Scheme : func () * runtime.Scheme {
353+ scheme := runtime .NewScheme ()
354+ _ = kcpapis .AddToScheme (scheme )
355+ _ = kcpcore .AddToScheme (scheme )
356+ return scheme
357+ }(),
358+ ManagerOpts : ctrl.Options {
359+ Metrics : server.Options {BindAddress : "0" },
360+ Scheme : func () * runtime.Scheme {
361+ scheme := runtime .NewScheme ()
362+ _ = kcpapis .AddToScheme (scheme )
363+ _ = kcpcore .AddToScheme (scheme )
364+ return scheme
365+ }(),
366+ },
367+ },
368+ log ,
369+ )
370+ require .NoError (t , err )
371+
372+ // Test multiple calls to Reconcile - should always return empty result and no error
373+ for i := 0 ; i < 5 ; i ++ {
374+ req := ctrl.Request {
375+ NamespacedName : types.NamespacedName {
376+ Name : "test-resource" ,
377+ Namespace : "test-namespace" ,
378+ },
379+ }
380+
381+ result , err := reconciler .Reconcile (context .Background (), req )
382+ assert .NoError (t , err , "Reconcile should never return an error (iteration %d)" , i )
383+ assert .Equal (t , ctrl.Result {}, result , "Reconcile should always return empty result (iteration %d)" , i )
384+ }
385+ }
386+
387+ func TestKCPReconciler_NewKCPReconciler_Coverage_Improvements (t * testing.T ) {
388+ log := testlogger .New ().HideLogOutput ().Logger
389+
390+ tests := []struct {
391+ name string
392+ appCfg config.Config
393+ opts reconciler.ReconcilerOpts
394+ expectedError bool
395+ errorContains string
396+ }{
397+ {
398+ name : "invalid_openapi_definitions_path" ,
399+ appCfg : config.Config {
400+ OpenApiDefinitionsPath : "/invalid/path/that/definitely/does/not/exist/anywhere" ,
401+ },
402+ opts : reconciler.ReconcilerOpts {
403+ Config : & rest.Config {Host : "https://kcp.example.com" },
404+ Scheme : func () * runtime.Scheme {
405+ scheme := runtime .NewScheme ()
406+ _ = kcpapis .AddToScheme (scheme )
407+ _ = kcpcore .AddToScheme (scheme )
408+ return scheme
409+ }(),
410+ ManagerOpts : ctrl.Options {
411+ Metrics : server.Options {BindAddress : "0" },
412+ },
413+ },
414+ expectedError : true ,
415+ errorContains : "failed to create or access schemas directory" ,
416+ },
417+ {
418+ name : "success_case_with_valid_temp_dir" ,
419+ appCfg : config.Config {
420+ OpenApiDefinitionsPath : t .TempDir (),
421+ },
422+ opts : reconciler.ReconcilerOpts {
423+ Config : & rest.Config {Host : "https://kcp.example.com" },
424+ Scheme : func () * runtime.Scheme {
425+ scheme := runtime .NewScheme ()
426+ _ = kcpapis .AddToScheme (scheme )
427+ _ = kcpcore .AddToScheme (scheme )
428+ return scheme
429+ }(),
430+ ManagerOpts : ctrl.Options {
431+ Metrics : server.Options {BindAddress : "0" },
432+ Scheme : func () * runtime.Scheme {
433+ scheme := runtime .NewScheme ()
434+ _ = kcpapis .AddToScheme (scheme )
435+ _ = kcpcore .AddToScheme (scheme )
436+ return scheme
437+ }(),
438+ },
439+ },
440+ expectedError : false ,
441+ },
442+ }
443+
444+ for _ , tt := range tests {
445+ t .Run (tt .name , func (t * testing.T ) {
446+ reconciler , err := kcp .NewKCPReconciler (tt .appCfg , tt .opts , log )
447+
448+ if tt .expectedError {
449+ assert .Error (t , err )
450+ if tt .errorContains != "" {
451+ assert .Contains (t , err .Error (), tt .errorContains )
452+ }
453+ assert .Nil (t , reconciler )
454+ } else {
455+ assert .NoError (t , err )
456+ assert .NotNil (t , reconciler )
457+ assert .NotNil (t , reconciler .GetManager ())
458+ }
459+ })
460+ }
461+ }
462+
463+ func TestKCPReconciler_NewKCPReconciler_ErrorCases (t * testing.T ) {
464+ tests := []struct {
465+ name string
466+ setupTest func () (config.Config , reconciler.ReconcilerOpts , * testlogger.TestLogger )
467+ expectedError bool
468+ errorContains string
469+ }{
470+ {
471+ name : "nil_logger" ,
472+ setupTest : func () (config.Config , reconciler.ReconcilerOpts , * testlogger.TestLogger ) {
473+ appCfg := config.Config {
474+ OpenApiDefinitionsPath : t .TempDir (),
475+ }
476+ opts := reconciler.ReconcilerOpts {
477+ Config : & rest.Config {Host : "https://kcp.example.com" },
478+ Scheme : runtime .NewScheme (),
479+ }
480+ return appCfg , opts , nil
481+ },
482+ expectedError : true ,
483+ errorContains : "logger should not be nil" ,
484+ },
485+ {
486+ name : "invalid_config_for_provider" ,
487+ setupTest : func () (config.Config , reconciler.ReconcilerOpts , * testlogger.TestLogger ) {
488+ log := testlogger .New ().HideLogOutput ()
489+ appCfg := config.Config {
490+ OpenApiDefinitionsPath : t .TempDir (),
491+ }
492+ opts := reconciler.ReconcilerOpts {
493+ Config : & rest.Config {}, // Empty config should cause provider creation to fail
494+ Scheme : func () * runtime.Scheme {
495+ scheme := runtime .NewScheme ()
496+ _ = kcpapis .AddToScheme (scheme )
497+ _ = kcpcore .AddToScheme (scheme )
498+ return scheme
499+ }(),
500+ }
501+ return appCfg , opts , log
502+ },
503+ expectedError : true ,
504+ errorContains : "failed to create" ,
505+ },
506+ }
507+
508+ for _ , tt := range tests {
509+ t .Run (tt .name , func (t * testing.T ) {
510+ appCfg , opts , log := tt .setupTest ()
511+
512+ var actualLogger * logger.Logger
513+ if log != nil {
514+ actualLogger = log .Logger
515+ }
516+
517+ reconciler , err := kcp .NewKCPReconciler (appCfg , opts , actualLogger )
518+
519+ if tt .expectedError {
520+ assert .Error (t , err )
521+ if tt .errorContains != "" {
522+ assert .Contains (t , err .Error (), tt .errorContains )
523+ }
524+ assert .Nil (t , reconciler )
525+ } else {
526+ assert .NoError (t , err )
527+ assert .NotNil (t , reconciler )
528+ }
529+ })
530+ }
177531}
0 commit comments