github.com/kubeshop/testkube@v1.17.23/pkg/repository/result/mongo_integration_test.go (about) 1 //go:build integration 2 3 package result 4 5 import ( 6 "context" 7 "fmt" 8 random "math/rand" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 14 "github.com/kubeshop/testkube/pkg/utils/test" 15 16 "github.com/kubeshop/testkube/pkg/datefilter" 17 "github.com/kubeshop/testkube/pkg/repository/storage" 18 19 "github.com/stretchr/testify/require" 20 21 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 22 "github.com/kubeshop/testkube/pkg/rand" 23 ) 24 25 const ( 26 mongoDns = "mongodb://localhost:27017" 27 mongoDbName = "testkube-test" 28 ) 29 30 func TestStorage_Integration(t *testing.T) { 31 test.IntegrationTest(t) 32 assert := require.New(t) 33 34 repository, err := getRepository() 35 assert.NoError(err) 36 37 err = repository.ResultsColl.Drop(context.TODO()) 38 assert.NoError(err) 39 40 oneDayAgo := time.Now().Add(-24 * time.Hour) 41 twoDaysAgo := time.Now().Add(-48 * time.Hour) 42 defaultName := "name" 43 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, time.Now(), map[string]string{"key1": "value1", "key2": "value2"}) 44 assert.NoError(err) 45 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, time.Now(), map[string]string{"key1": "value1", "key2": "value2"}) 46 assert.NoError(err) 47 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, time.Now(), map[string]string{"key3": "value3", "key4": "value4"}) 48 assert.NoError(err) 49 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, time.Now(), map[string]string{"key3": "value3", "key4": "value4"}) 50 assert.NoError(err) 51 err = repository.insertExecutionResult(defaultName, testkube.PASSED_ExecutionStatus, time.Now(), map[string]string{"key1": "value1", "key4": "value4"}) 52 assert.NoError(err) 53 err = repository.insertExecutionResult(defaultName, testkube.QUEUED_ExecutionStatus, time.Now(), map[string]string{"key1": "value1", "key3": "value3"}) 54 assert.NoError(err) 55 err = repository.insertExecutionResult(defaultName, testkube.RUNNING_ExecutionStatus, time.Now(), map[string]string{"key5": "value5", "key6": "value6"}) 56 assert.NoError(err) 57 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, oneDayAgo, map[string]string{"key1": "value1", "key5": "value5"}) 58 assert.NoError(err) 59 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, oneDayAgo, map[string]string{"key1": "value1", "key6": "value6"}) 60 assert.NoError(err) 61 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, oneDayAgo, map[string]string{"key2": "value2", "key4": "value4"}) 62 assert.NoError(err) 63 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, oneDayAgo, map[string]string{"key2": "value2", "key5": "value5"}) 64 assert.NoError(err) 65 err = repository.insertExecutionResult(defaultName, testkube.PASSED_ExecutionStatus, oneDayAgo, map[string]string{"key7": "value7", "key8": "value8"}) 66 assert.NoError(err) 67 err = repository.insertExecutionResult(defaultName, testkube.QUEUED_ExecutionStatus, oneDayAgo, map[string]string{"key7": "value7", "key8": "value8"}) 68 assert.NoError(err) 69 err = repository.insertExecutionResult(defaultName, testkube.RUNNING_ExecutionStatus, oneDayAgo, map[string]string{"key7": "value7", "key8": "value8"}) 70 assert.NoError(err) 71 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, twoDaysAgo, map[string]string{"key7": "value7", "key8": "value8"}) 72 assert.NoError(err) 73 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, twoDaysAgo, map[string]string{"key1": "value1", "key2": "value2"}) 74 assert.NoError(err) 75 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, twoDaysAgo, map[string]string{"key1": "value1", "key2": "value2"}) 76 assert.NoError(err) 77 err = repository.insertExecutionResult(defaultName, testkube.FAILED_ExecutionStatus, twoDaysAgo, map[string]string{"key1": "value1", "key2": "value2"}) 78 assert.NoError(err) 79 err = repository.insertExecutionResult(defaultName, testkube.PASSED_ExecutionStatus, twoDaysAgo, map[string]string{"key3": "value3", "key6": "value6"}) 80 assert.NoError(err) 81 err = repository.insertExecutionResult(defaultName, testkube.QUEUED_ExecutionStatus, twoDaysAgo, map[string]string{"key3": "value3", "key5": "value5"}) 82 assert.NoError(err) 83 err = repository.insertExecutionResult(defaultName, testkube.RUNNING_ExecutionStatus, twoDaysAgo, map[string]string{"key4": "value4", "key6": "value6"}) 84 assert.NoError(err) 85 86 numberOfLabels := 8 87 88 t.Run("filter with status should return only executions with that status", func(t *testing.T) { 89 90 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithStatus(string(testkube.FAILED_ExecutionStatus))) 91 assert.NoError(err) 92 assert.Len(executions, 12) 93 assert.Equal(*executions[0].ExecutionResult.Status, testkube.FAILED_ExecutionStatus) 94 }) 95 96 t.Run("filter with different statuses should return only executions with those statuses", func(t *testing.T) { 97 98 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithStatus( 99 string(testkube.FAILED_ExecutionStatus)+","+string(testkube.PASSED_ExecutionStatus))) 100 assert.NoError(err) 101 assert.Len(executions, 15) 102 }) 103 104 t.Run("filter with status should return only totals with that status", func(t *testing.T) { 105 filteredTotals, err := repository.GetExecutionTotals(context.Background(), false, NewExecutionsFilter().WithStatus(string(testkube.FAILED_ExecutionStatus))) 106 107 assert.NoError(err) 108 assert.Equal(int32(12), filteredTotals.Results) 109 assert.Equal(int32(12), filteredTotals.Failed) 110 assert.Equal(int32(0), filteredTotals.Passed) 111 assert.Equal(int32(0), filteredTotals.Queued) 112 assert.Equal(int32(0), filteredTotals.Running) 113 }) 114 115 t.Run("getting totals without filters should return all the executions", func(t *testing.T) { 116 totals, err := repository.GetExecutionTotals(context.Background(), false) 117 118 assert.NoError(err) 119 assert.Equal(int32(21), totals.Results) 120 assert.Equal(int32(12), totals.Failed) 121 assert.Equal(int32(3), totals.Passed) 122 assert.Equal(int32(3), totals.Queued) 123 assert.Equal(int32(3), totals.Running) 124 }) 125 126 dateFilter := datefilter.NewDateFilter(oneDayAgo.Format(datefilter.DateFormatISO8601), "") 127 assert.True(dateFilter.IsStartValid) 128 129 t.Run("filter with startDate should return only executions after that day", func(t *testing.T) { 130 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithStartDate(dateFilter.Start)) 131 assert.NoError(err) 132 assert.Len(executions, 14) 133 assert.True(executions[0].StartTime.After(dateFilter.Start) || executions[0].StartTime.Equal(dateFilter.Start)) 134 }) 135 136 t.Run("filter with labels should return only filters with given labels", func(t *testing.T) { 137 138 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithSelector("key1=value1,key2=value2")) 139 assert.NoError(err) 140 assert.Len(executions, 5) 141 }) 142 143 t.Run("filter with labels should return only filters with existing labels", func(t *testing.T) { 144 145 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithSelector("key1")) 146 assert.NoError(err) 147 assert.Len(executions, 9) 148 }) 149 150 t.Run("getting totals with filter by date start date should return only the results after this date", func(t *testing.T) { 151 totals, err := repository.GetExecutionTotals(context.Background(), false, NewExecutionsFilter().WithStartDate(dateFilter.Start)) 152 153 assert.NoError(err) 154 assert.Equal(int32(14), totals.Results) 155 assert.Equal(int32(8), totals.Failed) 156 assert.Equal(int32(2), totals.Passed) 157 assert.Equal(int32(2), totals.Queued) 158 assert.Equal(int32(2), totals.Running) 159 }) 160 161 dateFilter = datefilter.NewDateFilter("", oneDayAgo.Format(datefilter.DateFormatISO8601)) 162 assert.True(dateFilter.IsEndValid) 163 164 t.Run("filter with endDate should return only executions before that day", func(t *testing.T) { 165 166 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithEndDate(dateFilter.End)) 167 assert.NoError(err) 168 assert.Len(executions, 7) 169 assert.True(executions[0].StartTime.Before(dateFilter.End) || executions[0].StartTime.Equal(dateFilter.End)) 170 }) 171 172 t.Run("getting totals with filter by date start date should return only the results before this date", func(t *testing.T) { 173 totals, err := repository.GetExecutionTotals(context.Background(), false, NewExecutionsFilter().WithEndDate(dateFilter.End)) 174 175 assert.NoError(err) 176 assert.Equal(int32(7), totals.Results) 177 assert.Equal(int32(4), totals.Failed) 178 assert.Equal(int32(1), totals.Passed) 179 assert.Equal(int32(1), totals.Queued) 180 assert.Equal(int32(1), totals.Running) 181 }) 182 183 t.Run("filter with test name that doesn't exist should return 0 results", func(t *testing.T) { 184 185 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithTestName("noneExisting")) 186 assert.NoError(err) 187 assert.Empty(executions) 188 }) 189 190 t.Run("getting totals with test name that doesn't exist should return 0 results", func(t *testing.T) { 191 totals, err := repository.GetExecutionTotals(context.Background(), false, NewExecutionsFilter().WithTestName("noneExisting")) 192 193 assert.NoError(err) 194 assert.Equal(int32(0), totals.Results) 195 assert.Equal(int32(0), totals.Failed) 196 assert.Equal(int32(0), totals.Passed) 197 assert.Equal(int32(0), totals.Queued) 198 assert.Equal(int32(0), totals.Running) 199 }) 200 201 t.Run("filter with ccombined filter should return corresponding results", func(t *testing.T) { 202 filter := NewExecutionsFilter(). 203 WithStatus(string(testkube.PASSED_ExecutionStatus)). 204 WithStartDate(twoDaysAgo). 205 WithEndDate(oneDayAgo). 206 WithTestName(defaultName) 207 208 executions, err := repository.GetExecutions(context.Background(), filter) 209 210 assert.NoError(err) 211 assert.Len(executions, 2) 212 }) 213 214 t.Run("getting totals with ccombined filter should return corresponding results", func(t *testing.T) { 215 filter := NewExecutionsFilter(). 216 WithStatus(string(testkube.PASSED_ExecutionStatus)). 217 WithStartDate(twoDaysAgo). 218 WithEndDate(oneDayAgo). 219 WithTestName(defaultName) 220 totals, err := repository.GetExecutionTotals(context.Background(), false, filter) 221 222 assert.NoError(err) 223 assert.Equal(int32(2), totals.Results) 224 assert.Equal(int32(0), totals.Failed) 225 assert.Equal(int32(2), totals.Passed) 226 assert.Equal(int32(0), totals.Queued) 227 assert.Equal(int32(0), totals.Running) 228 }) 229 230 name := "someDifferentName" 231 err = repository.insertExecutionResult(name, testkube.RUNNING_ExecutionStatus, twoDaysAgo, nil) 232 assert.NoError(err) 233 234 t.Run("filter with test name should return result only for that test name", func(t *testing.T) { 235 236 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter().WithTestName(name)) 237 assert.NoError(err) 238 assert.Len(executions, 1) 239 assert.Equal(executions[0].TestName, name) 240 }) 241 242 t.Run("getting totals with test name should return result only for that test name", func(t *testing.T) { 243 totals, err := repository.GetExecutionTotals(context.Background(), false, NewExecutionsFilter().WithTestName(name)) 244 245 assert.NoError(err) 246 assert.Equal(int32(1), totals.Results) 247 assert.Equal(int32(0), totals.Failed) 248 assert.Equal(int32(0), totals.Passed) 249 assert.Equal(int32(0), totals.Queued) 250 assert.Equal(int32(1), totals.Running) 251 }) 252 253 t.Run("test executions should be sorted with most recent first", func(t *testing.T) { 254 executions, err := repository.GetExecutions(context.Background(), NewExecutionsFilter()) 255 assert.NoError(err) 256 assert.NotEmpty(executions) 257 assert.True(executions[0].StartTime.After(executions[len(executions)-1].StartTime), "executions are not sorted with the most recent first") 258 }) 259 260 t.Run("getting labels should return all available labels", func(t *testing.T) { 261 labels, err := repository.GetLabels(context.Background()) 262 assert.NoError(err) 263 assert.Len(labels, numberOfLabels) 264 }) 265 266 } 267 268 func TestLabels_Integration(t *testing.T) { 269 test.IntegrationTest(t) 270 assert := require.New(t) 271 272 repository, err := getRepository() 273 assert.NoError(err) 274 275 err = repository.ResultsColl.Drop(context.TODO()) 276 assert.NoError(err) 277 278 t.Run("getting labels when there are no labels should return empty map", func(t *testing.T) { 279 labels, err := repository.GetLabels(context.Background()) 280 assert.NoError(err) 281 assert.Len(labels, 0) 282 }) 283 } 284 285 func TestTestExecutionsMetrics_Integration(t *testing.T) { 286 test.IntegrationTest(t) 287 assert := require.New(t) 288 289 repository, err := getRepository() 290 assert.NoError(err) 291 292 err = repository.ResultsColl.Drop(context.TODO()) 293 assert.NoError(err) 294 295 testName := "example-test" 296 297 err = repository.insertExecutionResult(testName, testkube.FAILED_ExecutionStatus, time.Now().Add(48*-time.Hour), map[string]string{"key1": "value1", "key2": "value2"}) 298 assert.NoError(err) 299 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Hour), map[string]string{"key1": "value1", "key2": "value2"}) 300 assert.NoError(err) 301 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(10*-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 302 assert.NoError(err) 303 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(10*-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 304 assert.NoError(err) 305 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 306 assert.NoError(err) 307 err = repository.insertExecutionResult(testName, testkube.FAILED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key1": "value1", "key2": "value2"}) 308 assert.NoError(err) 309 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key1": "value1", "key2": "value2"}) 310 assert.NoError(err) 311 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 312 assert.NoError(err) 313 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 314 assert.NoError(err) 315 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 316 assert.NoError(err) 317 err = repository.insertExecutionResult(testName, testkube.FAILED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key1": "value1", "key2": "value2"}) 318 assert.NoError(err) 319 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key1": "value1", "key2": "value2"}) 320 assert.NoError(err) 321 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 322 assert.NoError(err) 323 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 324 assert.NoError(err) 325 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 326 assert.NoError(err) 327 err = repository.insertExecutionResult(testName, testkube.FAILED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key1": "value1", "key2": "value2"}) 328 assert.NoError(err) 329 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key1": "value1", "key2": "value2"}) 330 assert.NoError(err) 331 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 332 assert.NoError(err) 333 err = repository.insertExecutionResult(testName, testkube.FAILED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 334 assert.NoError(err) 335 err = repository.insertExecutionResult(testName, testkube.PASSED_ExecutionStatus, time.Now().Add(-time.Minute), map[string]string{"key3": "value3", "key4": "value4"}) 336 assert.NoError(err) 337 338 metrics, err := repository.GetTestMetrics(context.Background(), testName, 100, 100) 339 assert.NoError(err) 340 341 t.Run("getting execution metrics for test data", func(t *testing.T) { 342 assert.NoError(err) 343 assert.Equal(int32(20), metrics.TotalExecutions) 344 assert.Equal(int32(5), metrics.FailedExecutions) 345 assert.Len(metrics.Executions, 20) 346 }) 347 348 t.Run("getting pass/fail ratio", func(t *testing.T) { 349 assert.Equal(float64(75), metrics.PassFailRatio) 350 }) 351 352 t.Run("getting percentiles of execution duration", func(t *testing.T) { 353 assert.Contains(metrics.ExecutionDurationP50, "1m0") 354 assert.Contains(metrics.ExecutionDurationP90, "10m0") 355 assert.Contains(metrics.ExecutionDurationP99, "48h0m0s") 356 }) 357 358 t.Run("limit should limit executions", func(t *testing.T) { 359 metrics, err := repository.GetTestMetrics(context.Background(), testName, 1, 100) 360 assert.NoError(err) 361 assert.Equal(1, len(metrics.Executions)) 362 }) 363 364 t.Run("filter last n days should limit executions", func(t *testing.T) { 365 metrics, err := repository.GetTestMetrics(context.Background(), testName, 100, 1) 366 assert.NoError(err) 367 assert.Equal(int32(19), metrics.TotalExecutions) 368 }) 369 } 370 371 func getRepository() (*MongoRepository, error) { 372 db, err := storage.GetMongoDatabase(mongoDns, mongoDbName, storage.TypeMongoDB, false, nil) 373 repository := NewMongoRepository(db, true, false) 374 return repository, err 375 } 376 377 func (r *MongoRepository) insertExecutionResult(testName string, execStatus testkube.ExecutionStatus, startTime time.Time, labels map[string]string) error { 378 return r.Insert(context.Background(), 379 testkube.Execution{ 380 Id: rand.Name(), 381 TestName: testName, 382 Name: "dummyName", 383 TestType: "test/curl", 384 StartTime: startTime, 385 EndTime: time.Now(), 386 Duration: time.Since(startTime).String(), 387 ExecutionResult: &testkube.ExecutionResult{Status: &execStatus}, 388 Labels: labels, 389 }) 390 } 391 392 func TestUpdateOutput_Integration(t *testing.T) { 393 test.IntegrationTest(t) 394 395 repository, err := getRepository() 396 assert.NoError(t, err) 397 398 testName := "example-test" 399 executionID := "example-execution" 400 401 err = repository.insertExecutionResult(testName, testkube.FAILED_ExecutionStatus, time.Now(), map[string]string{"key1": "value1", "key2": "value2"}) 402 assert.NoError(t, err) 403 404 randomString := func(len int) string { 405 bytes := make([]byte, len) 406 for i := 0; i < len; i++ { 407 bytes[i] = byte(65 + random.Intn(25)) 408 } 409 return string(bytes) 410 } 411 412 t.Run("valid input", func(t *testing.T) { 413 result := testkube.Execution{ 414 Id: executionID, 415 Name: executionID, 416 TestName: testName, 417 ExecutionResult: &testkube.ExecutionResult{ 418 Output: "", 419 }, 420 } 421 err = repository.UpdateResult(context.Background(), testName, result) 422 assert.NoError(t, err) 423 }) 424 t.Run("output bigger than MongoDB document limit should not throw error", func(t *testing.T) { 425 result := testkube.Execution{ 426 Id: executionID, 427 Name: executionID, 428 TestName: testName, 429 ExecutionResult: &testkube.ExecutionResult{ 430 Output: randomString(OutputMaxSize), 431 }, 432 } 433 err = repository.UpdateResult(context.Background(), testName, result) 434 assert.NoError(t, err) 435 }) 436 t.Run("too many steps", func(t *testing.T) { 437 result := testkube.Execution{ 438 Id: executionID, 439 Name: executionID, 440 TestName: testName, 441 ExecutionResult: &testkube.ExecutionResult{ 442 Output: randomString(OutputMaxSize), 443 Steps: []testkube.ExecutionStepResult{}, 444 }, 445 } 446 for i := 0; i < StepMaxCount; i++ { 447 result.ExecutionResult.Steps = append(result.ExecutionResult.Steps, testkube.ExecutionStepResult{ 448 Name: fmt.Sprintf("step-%d", i), 449 }) 450 } 451 452 err = repository.UpdateResult(context.Background(), testName, result) 453 assert.NoError(t, err) 454 }) 455 }