github.com/kubeshop/testkube@v1.17.23/internal/app/api/v1/executions_test.go (about) 1 package v1 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http/httptest" 7 "testing" 8 "time" 9 10 "github.com/kubeshop/testkube/pkg/featureflags" 11 "github.com/kubeshop/testkube/pkg/repository/result" 12 13 "github.com/gofiber/fiber/v2" 14 "github.com/golang/mock/gomock" 15 "github.com/stretchr/testify/assert" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/runtime" 18 k8sclient "sigs.k8s.io/controller-runtime/pkg/client" 19 "sigs.k8s.io/controller-runtime/pkg/client/fake" 20 21 executorv1 "github.com/kubeshop/testkube-operator/api/executor/v1" 22 executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1" 23 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 24 executorclient "github.com/kubeshop/testkube/pkg/executor/client" 25 "github.com/kubeshop/testkube/pkg/executor/output" 26 "github.com/kubeshop/testkube/pkg/log" 27 logclient "github.com/kubeshop/testkube/pkg/logs/client" 28 "github.com/kubeshop/testkube/pkg/logs/events" 29 "github.com/kubeshop/testkube/pkg/server" 30 ) 31 32 func TestTestkubeAPI_ExecutionLogsHandler(t *testing.T) { 33 app := fiber.New() 34 resultRepo := MockExecutionResultsRepository{} 35 executor := &MockExecutor{} 36 s := &TestkubeAPI{ 37 HTTPServer: server.HTTPServer{ 38 Mux: app, 39 Log: log.DefaultLogger, 40 }, 41 ExecutionResults: &resultRepo, 42 ExecutorsClient: getMockExecutorClient(), 43 Executor: executor, 44 } 45 app.Get("/executions/:executionID/logs", s.ExecutionLogsHandler()) 46 47 tests := []struct { 48 name string 49 route string 50 expectedCode int 51 execution testkube.Execution 52 jobLogs testkube.ExecutorOutput 53 wantLogs string 54 }{ 55 { 56 name: "Test getting execution from result output", 57 route: "/executions/finished-1234/logs", 58 expectedCode: 200, 59 execution: testkube.Execution{ 60 Id: "finished-1234", 61 ExecutionResult: &testkube.ExecutionResult{ 62 Status: testkube.StatusPtr(testkube.PASSED_ExecutionStatus), 63 Output: "storage logs", 64 }, 65 }, 66 wantLogs: "storage logs", 67 }, 68 { 69 name: "Test getting execution from job", 70 route: "/executions/running-1234/logs", 71 expectedCode: 200, 72 execution: testkube.Execution{ 73 Id: "running-1234", 74 TestType: "curl/test", 75 ExecutionResult: &testkube.ExecutionResult{ 76 Status: testkube.StatusPtr(testkube.RUNNING_ExecutionStatus), 77 }, 78 }, 79 jobLogs: testkube.ExecutorOutput{ 80 Type_: output.TypeLogLine, 81 Content: "job logs", 82 }, 83 wantLogs: "job logs", 84 }, 85 } 86 responsePrefix := "data: " 87 for _, tt := range tests { 88 t.Run(tt.name, func(t *testing.T) { 89 resultRepo.GetFn = func(ctx context.Context, id string) (testkube.Execution, error) { 90 assert.Equal(t, tt.execution.Id, id) 91 92 return tt.execution, nil 93 } 94 executor.LogsFn = func(id string) (out chan output.Output, err error) { 95 assert.Equal(t, tt.execution.Id, id) 96 97 out = make(chan output.Output) 98 go func() { 99 defer func() { 100 close(out) 101 }() 102 103 out <- output.Output(tt.jobLogs) 104 }() 105 return 106 } 107 108 req := httptest.NewRequest("GET", tt.route, nil) 109 resp, err := app.Test(req, -1) 110 assert.NoError(t, err) 111 defer resp.Body.Close() 112 113 assert.Equal(t, tt.expectedCode, resp.StatusCode, tt.name) 114 115 b := make([]byte, len(responsePrefix)) 116 resp.Body.Read(b) 117 assert.Equal(t, responsePrefix, string(b)) 118 119 var res output.Output 120 err = json.NewDecoder(resp.Body).Decode(&res) 121 assert.NoError(t, err) 122 assert.Equal(t, tt.wantLogs, res.Content) 123 }) 124 } 125 } 126 127 func TestTestkubeAPI_ExecutionLogsHandlerV2(t *testing.T) { 128 app := fiber.New() 129 130 mockCtrl := gomock.NewController(t) 131 defer mockCtrl.Finish() 132 133 grpcClient := logclient.NewMockStreamGetter(mockCtrl) 134 135 eventLog := events.Log{ 136 Content: "storage logs", 137 Source: events.SourceJobPod, 138 Version: string(events.LogVersionV2), 139 } 140 141 out := make(chan events.LogResponse) 142 go func() { 143 defer func() { 144 close(out) 145 }() 146 147 out <- events.LogResponse{Log: eventLog} 148 }() 149 150 grpcClient.EXPECT().Get(gomock.Any(), "test-execution-1").Return(out, nil) 151 s := &TestkubeAPI{ 152 HTTPServer: server.HTTPServer{ 153 Mux: app, 154 Log: log.DefaultLogger, 155 }, 156 featureFlags: featureflags.FeatureFlags{LogsV2: true}, 157 logGrpcClient: grpcClient, 158 } 159 app.Get("/executions/:executionID/logs/v2", s.ExecutionLogsHandlerV2()) 160 161 tests := []struct { 162 name string 163 route string 164 expectedCode int 165 eventLog events.Log 166 }{ 167 { 168 name: "Test getting logs from grpc client", 169 route: "/executions/test-execution-1/logs/v2", 170 expectedCode: 200, 171 eventLog: eventLog, 172 }, 173 } 174 175 responsePrefix := "data: " 176 for _, tt := range tests { 177 t.Run(tt.name, func(t *testing.T) { 178 req := httptest.NewRequest("GET", tt.route, nil) 179 resp, err := app.Test(req, -1) 180 assert.NoError(t, err) 181 defer resp.Body.Close() 182 183 assert.Equal(t, tt.expectedCode, resp.StatusCode, tt.name) 184 185 b := make([]byte, len(responsePrefix)) 186 resp.Body.Read(b) 187 assert.Equal(t, responsePrefix, string(b)) 188 189 var res events.Log 190 err = json.NewDecoder(resp.Body).Decode(&res) 191 assert.NoError(t, err) 192 assert.Equal(t, tt.eventLog, res) 193 }) 194 } 195 } 196 197 type MockExecutionResultsRepository struct { 198 GetFn func(ctx context.Context, id string) (testkube.Execution, error) 199 } 200 201 func (r MockExecutionResultsRepository) Get(ctx context.Context, id string) (testkube.Execution, error) { 202 if r.GetFn == nil { 203 panic("not implemented") 204 } 205 return r.GetFn(ctx, id) 206 } 207 208 func (r MockExecutionResultsRepository) GetExecution(ctx context.Context, id string) (testkube.Execution, error) { 209 if r.GetFn == nil { 210 panic("not implemented") 211 } 212 return r.GetFn(ctx, id) 213 } 214 215 func (r MockExecutionResultsRepository) GetByNameAndTest(ctx context.Context, name, testName string) (testkube.Execution, error) { 216 panic("not implemented") 217 } 218 219 func (r MockExecutionResultsRepository) GetLatestByTest(ctx context.Context, testName string) (*testkube.Execution, error) { 220 panic("not implemented") 221 } 222 223 func (r MockExecutionResultsRepository) GetLatestByTests(ctx context.Context, testNames []string) (executions []testkube.Execution, err error) { 224 panic("not implemented") 225 } 226 227 func (r MockExecutionResultsRepository) GetExecutions(ctx context.Context, filter result.Filter) ([]testkube.Execution, error) { 228 panic("not implemented") 229 } 230 231 func (r MockExecutionResultsRepository) GetExecutionTotals(ctx context.Context, paging bool, filter ...result.Filter) (result testkube.ExecutionsTotals, err error) { 232 panic("not implemented") 233 } 234 235 func (r MockExecutionResultsRepository) GetNextExecutionNumber(ctx context.Context, testName string) (int32, error) { 236 panic("not implemented") 237 } 238 239 func (r MockExecutionResultsRepository) Insert(ctx context.Context, result testkube.Execution) error { 240 panic("not implemented") 241 } 242 243 func (r MockExecutionResultsRepository) Update(ctx context.Context, result testkube.Execution) error { 244 panic("not implemented") 245 } 246 247 func (r MockExecutionResultsRepository) UpdateResult(ctx context.Context, id string, execution testkube.Execution) error { 248 panic("not implemented") 249 } 250 251 func (r MockExecutionResultsRepository) StartExecution(ctx context.Context, id string, startTime time.Time) error { 252 panic("not implemented") 253 } 254 255 func (r MockExecutionResultsRepository) EndExecution(ctx context.Context, execution testkube.Execution) error { 256 panic("not implemented") 257 } 258 259 func (r MockExecutionResultsRepository) GetLabels(ctx context.Context) (labels map[string][]string, err error) { 260 panic("not implemented") 261 } 262 263 func (r MockExecutionResultsRepository) DeleteByTest(ctx context.Context, testName string) error { 264 panic("not implemented") 265 } 266 267 func (r MockExecutionResultsRepository) DeleteByTestSuite(ctx context.Context, testSuiteName string) error { 268 panic("not implemented") 269 } 270 271 func (r MockExecutionResultsRepository) DeleteAll(ctx context.Context) error { 272 panic("not implemented") 273 } 274 275 func (r MockExecutionResultsRepository) DeleteByTests(ctx context.Context, testNames []string) error { 276 panic("not implemented") 277 } 278 279 func (r MockExecutionResultsRepository) DeleteByTestSuites(ctx context.Context, testSuiteNames []string) error { 280 panic("not implemented") 281 } 282 283 func (r MockExecutionResultsRepository) DeleteForAllTestSuites(ctx context.Context) error { 284 panic("not implemented") 285 } 286 287 func (r MockExecutionResultsRepository) GetTestMetrics(ctx context.Context, name string, limit, last int) (testkube.ExecutionsMetrics, error) { 288 panic("not implemented") 289 } 290 291 func (r MockExecutionResultsRepository) Count(ctx context.Context, filter result.Filter) (int64, error) { 292 panic("not implemented") 293 } 294 295 type MockExecutor struct { 296 LogsFn func(id string) (chan output.Output, error) 297 } 298 299 func (e MockExecutor) Execute(ctx context.Context, execution *testkube.Execution, options executorclient.ExecuteOptions) (*testkube.ExecutionResult, error) { 300 panic("not implemented") 301 } 302 303 func (e MockExecutor) Abort(ctx context.Context, execution *testkube.Execution) (*testkube.ExecutionResult, error) { 304 panic("not implemented") 305 } 306 307 func (e MockExecutor) Logs(ctx context.Context, id, namespace string) (chan output.Output, error) { 308 if e.LogsFn == nil { 309 panic("not implemented") 310 } 311 return e.LogsFn(id) 312 } 313 314 func getMockExecutorClient() *executorsclientv1.ExecutorsClient { 315 scheme := runtime.NewScheme() 316 executorv1.AddToScheme(scheme) 317 318 initObjects := []k8sclient.Object{ 319 &executorv1.Executor{ 320 TypeMeta: metav1.TypeMeta{ 321 Kind: "Executor", 322 APIVersion: "executor.testkube.io/v1", 323 }, 324 ObjectMeta: metav1.ObjectMeta{ 325 Name: "sample", 326 Namespace: "default", 327 }, 328 Spec: executorv1.ExecutorSpec{ 329 Types: []string{"curl/test"}, 330 ExecutorType: "", 331 JobTemplate: "", 332 JobTemplateReference: "", 333 }, 334 Status: executorv1.ExecutorStatus{}, 335 }, 336 } 337 338 fakeClient := fake.NewClientBuilder(). 339 WithScheme(scheme). 340 WithObjects(initObjects...). 341 Build() 342 return executorsclientv1.NewClient(fakeClient, "") 343 }