@@ -14,18 +14,192 @@ import (
1414 "testing"
1515 "time"
1616
17+ kservev1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1"
18+ kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1"
19+ lsdapi "github.com/llamastack/llama-stack-k8s-operator/api/v1alpha1"
1720 . "github.com/onsi/ginkgo/v2"
1821 . "github.com/onsi/gomega"
22+ gorchv1alpha1 "github.com/trustyai-explainability/trustyai-service-operator/api/gorch/v1alpha1"
23+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
24+ "k8s.io/apimachinery/pkg/runtime"
25+ clientgoscheme "k8s.io/client-go/kubernetes/scheme"
26+ "k8s.io/client-go/rest"
27+ "sigs.k8s.io/controller-runtime/pkg/client"
28+ "sigs.k8s.io/controller-runtime/pkg/envtest"
29+ logf "sigs.k8s.io/controller-runtime/pkg/log"
30+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
31+
32+ "github.com/opendatahub-io/gen-ai/internal/cache"
1933 "github.com/opendatahub-io/gen-ai/internal/config"
34+ "github.com/opendatahub-io/gen-ai/internal/integrations/kubernetes/k8smocks"
35+ "github.com/opendatahub-io/gen-ai/internal/integrations/llamastack/lsmocks"
36+ "github.com/opendatahub-io/gen-ai/internal/integrations/maas/maasmocks"
37+ "github.com/opendatahub-io/gen-ai/internal/integrations/mcp/mcpmocks"
38+ "github.com/opendatahub-io/gen-ai/internal/repositories"
39+ "github.com/opendatahub-io/gen-ai/internal/services"
40+ )
41+
42+ // Package-level test infrastructure - initialized once, shared by all tests.
43+ // WARNING: Tests using these shared resources must NOT use t.Parallel()
44+ // as they share cluster state.
45+ var (
46+ ctx context.Context
47+ cancel context.CancelFunc
48+ testK8sClient client.Client
49+ testCfg * rest.Config
50+ testEnv * envtest.Environment
51+ testScheme * runtime.Scheme
2052)
2153
22- // TestAPIHandlers is the main test suite entry point
54+ // TestMain sets up envtest for ALL tests (both regular Go tests and Ginkgo tests).
55+ // This is called once before any tests run and handles cleanup after all tests complete.
56+ func TestMain (m * testing.M ) {
57+ logf .SetLogger (zap .New (zap .UseDevMode (true )))
58+
59+ ctx , cancel = context .WithCancel (context .TODO ())
60+
61+ testScheme = runtime .NewScheme ()
62+ err := clientgoscheme .AddToScheme (testScheme )
63+ if err != nil {
64+ logf .Log .Error (err , "failed to add Kubernetes types to scheme" )
65+ os .Exit (1 )
66+ }
67+
68+ err = lsdapi .AddToScheme (testScheme )
69+ if err != nil {
70+ logf .Log .Error (err , "failed to add LlamaStackDistribution types to scheme" )
71+ os .Exit (1 )
72+ }
73+
74+ err = kservev1alpha1 .AddToScheme (testScheme )
75+ if err != nil {
76+ logf .Log .Error (err , "failed to add KServe v1alpha1 types to scheme" )
77+ os .Exit (1 )
78+ }
79+
80+ err = kservev1beta1 .AddToScheme (testScheme )
81+ if err != nil {
82+ logf .Log .Error (err , "failed to add KServe v1beta1 types to scheme" )
83+ os .Exit (1 )
84+ }
85+
86+ err = gorchv1alpha1 .AddToScheme (testScheme )
87+ if err != nil {
88+ logf .Log .Error (err , "failed to add GuardrailsOrchestrator (gorch) types to scheme" )
89+ os .Exit (1 )
90+ }
91+
92+ logf .Log .Info ("bootstrapping test environment" )
93+ binaryDir , err := getFirstFoundEnvTestBinaryDir ()
94+ if err != nil {
95+ logf .Log .Error (err , "failed to resolve envtest binary directory" )
96+ os .Exit (1 )
97+ }
98+ testEnv = & envtest.Environment {
99+ BinaryAssetsDirectory : binaryDir ,
100+ ControlPlaneStartTimeout : 60 * time .Second ,
101+ ControlPlaneStopTimeout : 60 * time .Second ,
102+ CRDs : []* apiextensionsv1.CustomResourceDefinition {
103+ k8smocks .CreateLlamaStackDistributionCRD (),
104+ k8smocks .CreateGuardrailsOrchestratorCRD (),
105+ },
106+ }
107+
108+ testCfg , err = testEnv .Start ()
109+ if err != nil {
110+ logf .Log .Error (err , "failed to start test environment" )
111+ os .Exit (1 )
112+ }
113+ if testCfg == nil {
114+ logf .Log .Error (nil , "testCfg is nil after starting test environment" )
115+ os .Exit (1 )
116+ }
117+
118+ testK8sClient , err = client .New (testCfg , client.Options {Scheme : testScheme })
119+ if err != nil {
120+ logf .Log .Error (err , "failed to create controller-runtime client" )
121+ os .Exit (1 )
122+ }
123+ if testK8sClient == nil {
124+ logf .Log .Error (nil , "testK8sClient is nil after creation" )
125+ os .Exit (1 )
126+ }
127+
128+ err = k8smocks .SetupMock (testK8sClient , ctx )
129+ if err != nil {
130+ logf .Log .Error (err , "failed to setup mock data" )
131+ os .Exit (1 )
132+ }
133+
134+ code := m .Run ()
135+
136+ // Find PID before stopping (needs to happen while envtest is still running)
137+ apiServerPID := k8smocks .FindAPIServerPID ()
138+ testEnvState := & k8smocks.TestEnvState {
139+ Env : testEnv ,
140+ APIServerPID : apiServerPID ,
141+ Ctx : ctx ,
142+ Cancel : cancel ,
143+ }
144+
145+ // Use proper cleanup instead of just testEnv.Stop()
146+ // This handles the Linux issue where envtest.Stop() fails to reap child processes
147+ k8smocks .CleanupTestEnvState (
148+ testEnvState ,
149+ func (format string , args ... interface {}) { logf .Log .Error (nil , fmt .Sprintf (format , args ... )) },
150+ func (format string , args ... interface {}) { logf .Log .Info (fmt .Sprintf (format , args ... )) },
151+ )
152+
153+ os .Exit (code )
154+ }
155+
156+ // getFirstFoundEnvTestBinaryDir returns the envtest binary assets directory.
157+ // ENVTEST_ASSETS is set by "make test"; when unset (e.g. IDE or ad-hoc go test),
158+ // it locates the module root via go.mod and uses <moduleRoot>/bin/k8s/<version-dir>.
159+ // Callers must fail fast on error; run "make test" or "make envtest" to ensure assets exist.
160+ func getFirstFoundEnvTestBinaryDir () (string , error ) {
161+ if envtestAssets := os .Getenv ("ENVTEST_ASSETS" ); envtestAssets != "" {
162+ return envtestAssets , nil
163+ }
164+
165+ cwd , err := os .Getwd ()
166+ if err != nil {
167+ return "" , fmt .Errorf ("get working directory: %w" , err )
168+ }
169+ projectRoot := cwd
170+ for {
171+ if _ , err := os .Stat (filepath .Join (projectRoot , "go.mod" )); err == nil {
172+ break
173+ }
174+ parent := filepath .Dir (projectRoot )
175+ if parent == projectRoot {
176+ return "" , fmt .Errorf ("project root (go.mod) not found when walking up from %s" , cwd )
177+ }
178+ projectRoot = parent
179+ }
180+
181+ basePath := filepath .Join (projectRoot , "bin" , "k8s" )
182+ entries , err := os .ReadDir (basePath )
183+ if err != nil {
184+ logf .Log .Error (err , "failed to read envtest binary directory" , "path" , basePath )
185+ return "" , fmt .Errorf ("read envtest binary dir %s: %w" , basePath , err )
186+ }
187+ for _ , entry := range entries {
188+ if entry .IsDir () {
189+ return filepath .Join (basePath , entry .Name ()), nil
190+ }
191+ }
192+ logf .Log .Error (nil , "no envtest version directory found" , "path" , basePath )
193+ return "" , fmt .Errorf ("no envtest version directory under %s (run make envtest)" , basePath )
194+ }
195+
196+ // TestAPIHandlers is the main test suite entry point for Ginkgo-based HTTP tests
23197func TestAPIHandlers (t * testing.T ) {
24198 RegisterFailHandler (Fail )
25199 RunSpecs (t , "API Handlers Suite" )
26200}
27201
28- // SharedTestContext holds common test infrastructure
202+ // SharedTestContext holds common test infrastructure for HTTP tests
29203type SharedTestContext struct {
30204 App * App
31205 Server * httptest.Server
@@ -36,32 +210,59 @@ type SharedTestContext struct {
36210
37211var testCtx * SharedTestContext
38212
213+ // BeforeSuite sets up HTTP test infrastructure for Ginkgo tests only.
214+ // It relies on TestMain() having already initialized envtest.
39215var _ = BeforeSuite (func () {
40- By ("Setting up test environment" )
216+ By ("Setting up HTTP test infrastructure" )
217+
218+ Expect (testK8sClient ).NotTo (BeNil ())
219+ Expect (testCfg ).NotTo (BeNil ())
41220
42221 logger := slog .New (slog .NewTextHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelDebug }))
43222
44- // Save current working directory
45223 originalWd , err := os .Getwd ()
46224 Expect (err ).NotTo (HaveOccurred ())
47225
48- // Change to project root directory so OpenAPI handler can find the YAML file
226+ // OpenAPI handler needs to find YAML file in project root
49227 projectRoot := filepath .Join (originalWd , ".." , ".." )
50228 err = os .Chdir (projectRoot )
51229 Expect (err ).NotTo (HaveOccurred ())
52230
53- // Create test app with mock configuration
54231 cfg := config.EnvConfig {
55232 Port : 8080 ,
56233 APIPathPrefix : "/api/v1" ,
57234 AuthMethod : config .AuthMethodDisabled ,
58235 MockLSClient : true ,
59236 }
60237
61- app , err := NewApp (cfg , logger )
238+ k8sFactory , err := k8smocks .NewTokenClientFactory (testK8sClient , testCfg , logger )
239+ Expect (err ).NotTo (HaveOccurred ())
240+
241+ openAPIHandler , err := NewOpenAPIHandler (logger )
62242 Expect (err ).NotTo (HaveOccurred ())
63243
64- // Restore original working directory
244+ memStore := cache .NewMemoryStore ()
245+ fileUploadJobTracker := services .NewFileUploadJobTracker (memStore , logger )
246+
247+ mcpFactory := mcpmocks .NewMockedMCPClientFactory (cfg , logger )
248+ // Create app manually to avoid NewApp() starting a second envtest instance
249+ app := & App {
250+ config : cfg ,
251+ logger : logger ,
252+ repositories : repositories .NewRepositoriesWithMCP (mcpFactory , logger ),
253+ openAPI : openAPIHandler ,
254+ kubernetesClientFactory : k8sFactory ,
255+ llamaStackClientFactory : lsmocks .NewMockClientFactory (),
256+ maasClientFactory : maasmocks .NewMockClientFactory (),
257+ mcpClientFactory : mcpFactory ,
258+ dashboardNamespace : "opendatahub" ,
259+ memoryStore : memStore ,
260+ rootCAs : nil ,
261+ clusterDomain : "" ,
262+ fileUploadJobTracker : fileUploadJobTracker ,
263+ testEnvState : nil ,
264+ }
265+
65266 err = os .Chdir (originalWd )
66267 Expect (err ).NotTo (HaveOccurred ())
67268
@@ -79,15 +280,14 @@ var _ = BeforeSuite(func() {
79280 Logger : logger ,
80281 }
81282
82- By ("Test environment setup complete" )
283+ By ("HTTP test environment setup complete" )
83284})
84285
85286var _ = AfterSuite (func () {
86- By ("Cleaning up test environment" )
287+ By ("tearing down HTTP test environment" )
87288 if testCtx != nil && testCtx .Server != nil {
88289 testCtx .Server .Close ()
89290 }
90- By ("Test environment cleanup complete" )
91291})
92292
93293// MakeRequest is a helper to make HTTP requests in tests
0 commit comments