github.com/nektos/act@v0.2.63/pkg/artifactcache/handler_test.go (about) 1 package artifactcache 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "path/filepath" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 "github.com/timshannon/bolthold" 18 "go.etcd.io/bbolt" 19 ) 20 21 func TestHandler(t *testing.T) { 22 dir := filepath.Join(t.TempDir(), "artifactcache") 23 handler, err := StartHandler(dir, "", 0, nil) 24 require.NoError(t, err) 25 26 base := fmt.Sprintf("%s%s", handler.ExternalURL(), urlBase) 27 28 defer func() { 29 t.Run("inpect db", func(t *testing.T) { 30 db, err := handler.openDB() 31 require.NoError(t, err) 32 defer db.Close() 33 require.NoError(t, db.Bolt().View(func(tx *bbolt.Tx) error { 34 return tx.Bucket([]byte("Cache")).ForEach(func(k, v []byte) error { 35 t.Logf("%s: %s", k, v) 36 return nil 37 }) 38 })) 39 }) 40 t.Run("close", func(t *testing.T) { 41 require.NoError(t, handler.Close()) 42 assert.Nil(t, handler.server) 43 assert.Nil(t, handler.listener) 44 _, err := http.Post(fmt.Sprintf("%s/caches/%d", base, 1), "", nil) 45 assert.Error(t, err) 46 }) 47 }() 48 49 t.Run("get not exist", func(t *testing.T) { 50 key := strings.ToLower(t.Name()) 51 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 52 resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version)) 53 require.NoError(t, err) 54 require.Equal(t, 204, resp.StatusCode) 55 }) 56 57 t.Run("reserve and upload", func(t *testing.T) { 58 key := strings.ToLower(t.Name()) 59 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 60 content := make([]byte, 100) 61 _, err := rand.Read(content) 62 require.NoError(t, err) 63 uploadCacheNormally(t, base, key, version, content) 64 }) 65 66 t.Run("clean", func(t *testing.T) { 67 resp, err := http.Post(fmt.Sprintf("%s/clean", base), "", nil) 68 require.NoError(t, err) 69 assert.Equal(t, 200, resp.StatusCode) 70 }) 71 72 t.Run("reserve with bad request", func(t *testing.T) { 73 body := []byte(`invalid json`) 74 require.NoError(t, err) 75 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 76 require.NoError(t, err) 77 assert.Equal(t, 400, resp.StatusCode) 78 }) 79 80 t.Run("duplicate reserve", func(t *testing.T) { 81 key := strings.ToLower(t.Name()) 82 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 83 var first, second struct { 84 CacheID uint64 `json:"cacheId"` 85 } 86 { 87 body, err := json.Marshal(&Request{ 88 Key: key, 89 Version: version, 90 Size: 100, 91 }) 92 require.NoError(t, err) 93 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 94 require.NoError(t, err) 95 assert.Equal(t, 200, resp.StatusCode) 96 97 require.NoError(t, json.NewDecoder(resp.Body).Decode(&first)) 98 assert.NotZero(t, first.CacheID) 99 } 100 { 101 body, err := json.Marshal(&Request{ 102 Key: key, 103 Version: version, 104 Size: 100, 105 }) 106 require.NoError(t, err) 107 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 108 require.NoError(t, err) 109 assert.Equal(t, 200, resp.StatusCode) 110 111 require.NoError(t, json.NewDecoder(resp.Body).Decode(&second)) 112 assert.NotZero(t, second.CacheID) 113 } 114 115 assert.NotEqual(t, first.CacheID, second.CacheID) 116 }) 117 118 t.Run("upload with bad id", func(t *testing.T) { 119 req, err := http.NewRequest(http.MethodPatch, 120 fmt.Sprintf("%s/caches/invalid_id", base), bytes.NewReader(nil)) 121 require.NoError(t, err) 122 req.Header.Set("Content-Type", "application/octet-stream") 123 req.Header.Set("Content-Range", "bytes 0-99/*") 124 resp, err := http.DefaultClient.Do(req) 125 require.NoError(t, err) 126 assert.Equal(t, 400, resp.StatusCode) 127 }) 128 129 t.Run("upload without reserve", func(t *testing.T) { 130 req, err := http.NewRequest(http.MethodPatch, 131 fmt.Sprintf("%s/caches/%d", base, 1000), bytes.NewReader(nil)) 132 require.NoError(t, err) 133 req.Header.Set("Content-Type", "application/octet-stream") 134 req.Header.Set("Content-Range", "bytes 0-99/*") 135 resp, err := http.DefaultClient.Do(req) 136 require.NoError(t, err) 137 assert.Equal(t, 400, resp.StatusCode) 138 }) 139 140 t.Run("upload with complete", func(t *testing.T) { 141 key := strings.ToLower(t.Name()) 142 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 143 var id uint64 144 content := make([]byte, 100) 145 _, err := rand.Read(content) 146 require.NoError(t, err) 147 { 148 body, err := json.Marshal(&Request{ 149 Key: key, 150 Version: version, 151 Size: 100, 152 }) 153 require.NoError(t, err) 154 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 155 require.NoError(t, err) 156 assert.Equal(t, 200, resp.StatusCode) 157 158 got := struct { 159 CacheID uint64 `json:"cacheId"` 160 }{} 161 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 162 id = got.CacheID 163 } 164 { 165 req, err := http.NewRequest(http.MethodPatch, 166 fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) 167 require.NoError(t, err) 168 req.Header.Set("Content-Type", "application/octet-stream") 169 req.Header.Set("Content-Range", "bytes 0-99/*") 170 resp, err := http.DefaultClient.Do(req) 171 require.NoError(t, err) 172 assert.Equal(t, 200, resp.StatusCode) 173 } 174 { 175 resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) 176 require.NoError(t, err) 177 assert.Equal(t, 200, resp.StatusCode) 178 } 179 { 180 req, err := http.NewRequest(http.MethodPatch, 181 fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) 182 require.NoError(t, err) 183 req.Header.Set("Content-Type", "application/octet-stream") 184 req.Header.Set("Content-Range", "bytes 0-99/*") 185 resp, err := http.DefaultClient.Do(req) 186 require.NoError(t, err) 187 assert.Equal(t, 400, resp.StatusCode) 188 } 189 }) 190 191 t.Run("upload with invalid range", func(t *testing.T) { 192 key := strings.ToLower(t.Name()) 193 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 194 var id uint64 195 content := make([]byte, 100) 196 _, err := rand.Read(content) 197 require.NoError(t, err) 198 { 199 body, err := json.Marshal(&Request{ 200 Key: key, 201 Version: version, 202 Size: 100, 203 }) 204 require.NoError(t, err) 205 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 206 require.NoError(t, err) 207 assert.Equal(t, 200, resp.StatusCode) 208 209 got := struct { 210 CacheID uint64 `json:"cacheId"` 211 }{} 212 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 213 id = got.CacheID 214 } 215 { 216 req, err := http.NewRequest(http.MethodPatch, 217 fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) 218 require.NoError(t, err) 219 req.Header.Set("Content-Type", "application/octet-stream") 220 req.Header.Set("Content-Range", "bytes xx-99/*") 221 resp, err := http.DefaultClient.Do(req) 222 require.NoError(t, err) 223 assert.Equal(t, 400, resp.StatusCode) 224 } 225 }) 226 227 t.Run("commit with bad id", func(t *testing.T) { 228 { 229 resp, err := http.Post(fmt.Sprintf("%s/caches/invalid_id", base), "", nil) 230 require.NoError(t, err) 231 assert.Equal(t, 400, resp.StatusCode) 232 } 233 }) 234 235 t.Run("commit with not exist id", func(t *testing.T) { 236 { 237 resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, 100), "", nil) 238 require.NoError(t, err) 239 assert.Equal(t, 400, resp.StatusCode) 240 } 241 }) 242 243 t.Run("duplicate commit", func(t *testing.T) { 244 key := strings.ToLower(t.Name()) 245 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 246 var id uint64 247 content := make([]byte, 100) 248 _, err := rand.Read(content) 249 require.NoError(t, err) 250 { 251 body, err := json.Marshal(&Request{ 252 Key: key, 253 Version: version, 254 Size: 100, 255 }) 256 require.NoError(t, err) 257 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 258 require.NoError(t, err) 259 assert.Equal(t, 200, resp.StatusCode) 260 261 got := struct { 262 CacheID uint64 `json:"cacheId"` 263 }{} 264 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 265 id = got.CacheID 266 } 267 { 268 req, err := http.NewRequest(http.MethodPatch, 269 fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) 270 require.NoError(t, err) 271 req.Header.Set("Content-Type", "application/octet-stream") 272 req.Header.Set("Content-Range", "bytes 0-99/*") 273 resp, err := http.DefaultClient.Do(req) 274 require.NoError(t, err) 275 assert.Equal(t, 200, resp.StatusCode) 276 } 277 { 278 resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) 279 require.NoError(t, err) 280 assert.Equal(t, 200, resp.StatusCode) 281 } 282 { 283 resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) 284 require.NoError(t, err) 285 assert.Equal(t, 400, resp.StatusCode) 286 } 287 }) 288 289 t.Run("commit early", func(t *testing.T) { 290 key := strings.ToLower(t.Name()) 291 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 292 var id uint64 293 content := make([]byte, 100) 294 _, err := rand.Read(content) 295 require.NoError(t, err) 296 { 297 body, err := json.Marshal(&Request{ 298 Key: key, 299 Version: version, 300 Size: 100, 301 }) 302 require.NoError(t, err) 303 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 304 require.NoError(t, err) 305 assert.Equal(t, 200, resp.StatusCode) 306 307 got := struct { 308 CacheID uint64 `json:"cacheId"` 309 }{} 310 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 311 id = got.CacheID 312 } 313 { 314 req, err := http.NewRequest(http.MethodPatch, 315 fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content[:50])) 316 require.NoError(t, err) 317 req.Header.Set("Content-Type", "application/octet-stream") 318 req.Header.Set("Content-Range", "bytes 0-59/*") 319 resp, err := http.DefaultClient.Do(req) 320 require.NoError(t, err) 321 assert.Equal(t, 200, resp.StatusCode) 322 } 323 { 324 resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) 325 require.NoError(t, err) 326 assert.Equal(t, 500, resp.StatusCode) 327 } 328 }) 329 330 t.Run("get with bad id", func(t *testing.T) { 331 resp, err := http.Get(fmt.Sprintf("%s/artifacts/invalid_id", base)) 332 require.NoError(t, err) 333 require.Equal(t, 400, resp.StatusCode) 334 }) 335 336 t.Run("get with not exist id", func(t *testing.T) { 337 resp, err := http.Get(fmt.Sprintf("%s/artifacts/%d", base, 100)) 338 require.NoError(t, err) 339 require.Equal(t, 404, resp.StatusCode) 340 }) 341 342 t.Run("get with not exist id", func(t *testing.T) { 343 resp, err := http.Get(fmt.Sprintf("%s/artifacts/%d", base, 100)) 344 require.NoError(t, err) 345 require.Equal(t, 404, resp.StatusCode) 346 }) 347 348 t.Run("get with multiple keys", func(t *testing.T) { 349 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 350 key := strings.ToLower(t.Name()) 351 keys := [3]string{ 352 key + "_a_b_c", 353 key + "_a_b", 354 key + "_a", 355 } 356 contents := [3][]byte{ 357 make([]byte, 100), 358 make([]byte, 200), 359 make([]byte, 300), 360 } 361 for i := range contents { 362 _, err := rand.Read(contents[i]) 363 require.NoError(t, err) 364 uploadCacheNormally(t, base, keys[i], version, contents[i]) 365 time.Sleep(time.Second) // ensure CreatedAt of caches are different 366 } 367 368 reqKeys := strings.Join([]string{ 369 key + "_a_b_x", 370 key + "_a_b", 371 key + "_a", 372 }, ",") 373 374 resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version)) 375 require.NoError(t, err) 376 require.Equal(t, 200, resp.StatusCode) 377 378 /* 379 Expect `key_a_b` because: 380 - `key_a_b_x" doesn't match any caches. 381 - `key_a_b" matches `key_a_b` and `key_a_b_c`, but `key_a_b` is newer. 382 */ 383 except := 1 384 385 got := struct { 386 Result string `json:"result"` 387 ArchiveLocation string `json:"archiveLocation"` 388 CacheKey string `json:"cacheKey"` 389 }{} 390 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 391 assert.Equal(t, "hit", got.Result) 392 assert.Equal(t, keys[except], got.CacheKey) 393 394 contentResp, err := http.Get(got.ArchiveLocation) 395 require.NoError(t, err) 396 require.Equal(t, 200, contentResp.StatusCode) 397 content, err := io.ReadAll(contentResp.Body) 398 require.NoError(t, err) 399 assert.Equal(t, contents[except], content) 400 }) 401 402 t.Run("case insensitive", func(t *testing.T) { 403 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 404 key := strings.ToLower(t.Name()) 405 content := make([]byte, 100) 406 _, err := rand.Read(content) 407 require.NoError(t, err) 408 uploadCacheNormally(t, base, key+"_ABC", version, content) 409 410 { 411 reqKey := key + "_aBc" 412 resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKey, version)) 413 require.NoError(t, err) 414 require.Equal(t, 200, resp.StatusCode) 415 got := struct { 416 Result string `json:"result"` 417 ArchiveLocation string `json:"archiveLocation"` 418 CacheKey string `json:"cacheKey"` 419 }{} 420 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 421 assert.Equal(t, "hit", got.Result) 422 assert.Equal(t, key+"_abc", got.CacheKey) 423 } 424 }) 425 426 t.Run("exact keys are preferred (key 0)", func(t *testing.T) { 427 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 428 key := strings.ToLower(t.Name()) 429 keys := [3]string{ 430 key + "_a", 431 key + "_a_b_c", 432 key + "_a_b", 433 } 434 contents := [3][]byte{ 435 make([]byte, 100), 436 make([]byte, 200), 437 make([]byte, 300), 438 } 439 for i := range contents { 440 _, err := rand.Read(contents[i]) 441 require.NoError(t, err) 442 uploadCacheNormally(t, base, keys[i], version, contents[i]) 443 time.Sleep(time.Second) // ensure CreatedAt of caches are different 444 } 445 446 reqKeys := strings.Join([]string{ 447 key + "_a", 448 key + "_a_b", 449 }, ",") 450 451 resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version)) 452 require.NoError(t, err) 453 require.Equal(t, 200, resp.StatusCode) 454 455 /* 456 Expect `key_a` because: 457 - `key_a` matches `key_a`, `key_a_b` and `key_a_b_c`, but `key_a` is an exact match. 458 - `key_a_b` matches `key_a_b` and `key_a_b_c`, but previous key had a match 459 */ 460 expect := 0 461 462 got := struct { 463 ArchiveLocation string `json:"archiveLocation"` 464 CacheKey string `json:"cacheKey"` 465 }{} 466 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 467 assert.Equal(t, keys[expect], got.CacheKey) 468 469 contentResp, err := http.Get(got.ArchiveLocation) 470 require.NoError(t, err) 471 require.Equal(t, 200, contentResp.StatusCode) 472 content, err := io.ReadAll(contentResp.Body) 473 require.NoError(t, err) 474 assert.Equal(t, contents[expect], content) 475 }) 476 477 t.Run("exact keys are preferred (key 1)", func(t *testing.T) { 478 version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" 479 key := strings.ToLower(t.Name()) 480 keys := [3]string{ 481 key + "_a", 482 key + "_a_b_c", 483 key + "_a_b", 484 } 485 contents := [3][]byte{ 486 make([]byte, 100), 487 make([]byte, 200), 488 make([]byte, 300), 489 } 490 for i := range contents { 491 _, err := rand.Read(contents[i]) 492 require.NoError(t, err) 493 uploadCacheNormally(t, base, keys[i], version, contents[i]) 494 time.Sleep(time.Second) // ensure CreatedAt of caches are different 495 } 496 497 reqKeys := strings.Join([]string{ 498 "------------------------------------------------------", 499 key + "_a", 500 key + "_a_b", 501 }, ",") 502 503 resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version)) 504 require.NoError(t, err) 505 require.Equal(t, 200, resp.StatusCode) 506 507 /* 508 Expect `key_a` because: 509 - `------------------------------------------------------` doesn't match any caches. 510 - `key_a` matches `key_a`, `key_a_b` and `key_a_b_c`, but `key_a` is an exact match. 511 - `key_a_b` matches `key_a_b` and `key_a_b_c`, but previous key had a match 512 */ 513 expect := 0 514 515 got := struct { 516 ArchiveLocation string `json:"archiveLocation"` 517 CacheKey string `json:"cacheKey"` 518 }{} 519 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 520 assert.Equal(t, keys[expect], got.CacheKey) 521 522 contentResp, err := http.Get(got.ArchiveLocation) 523 require.NoError(t, err) 524 require.Equal(t, 200, contentResp.StatusCode) 525 content, err := io.ReadAll(contentResp.Body) 526 require.NoError(t, err) 527 assert.Equal(t, contents[expect], content) 528 }) 529 } 530 531 func uploadCacheNormally(t *testing.T, base, key, version string, content []byte) { 532 var id uint64 533 { 534 body, err := json.Marshal(&Request{ 535 Key: key, 536 Version: version, 537 Size: int64(len(content)), 538 }) 539 require.NoError(t, err) 540 resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) 541 require.NoError(t, err) 542 assert.Equal(t, 200, resp.StatusCode) 543 544 got := struct { 545 CacheID uint64 `json:"cacheId"` 546 }{} 547 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 548 id = got.CacheID 549 } 550 { 551 req, err := http.NewRequest(http.MethodPatch, 552 fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) 553 require.NoError(t, err) 554 req.Header.Set("Content-Type", "application/octet-stream") 555 req.Header.Set("Content-Range", "bytes 0-99/*") 556 resp, err := http.DefaultClient.Do(req) 557 require.NoError(t, err) 558 assert.Equal(t, 200, resp.StatusCode) 559 } 560 { 561 resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) 562 require.NoError(t, err) 563 assert.Equal(t, 200, resp.StatusCode) 564 } 565 var archiveLocation string 566 { 567 resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version)) 568 require.NoError(t, err) 569 require.Equal(t, 200, resp.StatusCode) 570 got := struct { 571 Result string `json:"result"` 572 ArchiveLocation string `json:"archiveLocation"` 573 CacheKey string `json:"cacheKey"` 574 }{} 575 require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) 576 assert.Equal(t, "hit", got.Result) 577 assert.Equal(t, strings.ToLower(key), got.CacheKey) 578 archiveLocation = got.ArchiveLocation 579 } 580 { 581 resp, err := http.Get(archiveLocation) //nolint:gosec 582 require.NoError(t, err) 583 require.Equal(t, 200, resp.StatusCode) 584 got, err := io.ReadAll(resp.Body) 585 require.NoError(t, err) 586 assert.Equal(t, content, got) 587 } 588 } 589 590 func TestHandler_gcCache(t *testing.T) { 591 dir := filepath.Join(t.TempDir(), "artifactcache") 592 handler, err := StartHandler(dir, "", 0, nil) 593 require.NoError(t, err) 594 595 defer func() { 596 require.NoError(t, handler.Close()) 597 }() 598 599 now := time.Now() 600 601 cases := []struct { 602 Cache *Cache 603 Kept bool 604 }{ 605 { 606 // should be kept, since it's used recently and not too old. 607 Cache: &Cache{ 608 Key: "test_key_1", 609 Version: "test_version", 610 Complete: true, 611 UsedAt: now.Unix(), 612 CreatedAt: now.Add(-time.Hour).Unix(), 613 }, 614 Kept: true, 615 }, 616 { 617 // should be removed, since it's not complete and not used for a while. 618 Cache: &Cache{ 619 Key: "test_key_2", 620 Version: "test_version", 621 Complete: false, 622 UsedAt: now.Add(-(keepTemp + time.Second)).Unix(), 623 CreatedAt: now.Add(-(keepTemp + time.Hour)).Unix(), 624 }, 625 Kept: false, 626 }, 627 { 628 // should be removed, since it's not used for a while. 629 Cache: &Cache{ 630 Key: "test_key_3", 631 Version: "test_version", 632 Complete: true, 633 UsedAt: now.Add(-(keepUnused + time.Second)).Unix(), 634 CreatedAt: now.Add(-(keepUnused + time.Hour)).Unix(), 635 }, 636 Kept: false, 637 }, 638 { 639 // should be removed, since it's used but too old. 640 Cache: &Cache{ 641 Key: "test_key_3", 642 Version: "test_version", 643 Complete: true, 644 UsedAt: now.Unix(), 645 CreatedAt: now.Add(-(keepUsed + time.Second)).Unix(), 646 }, 647 Kept: false, 648 }, 649 { 650 // should be kept, since it has a newer edition but be used recently. 651 Cache: &Cache{ 652 Key: "test_key_1", 653 Version: "test_version", 654 Complete: true, 655 UsedAt: now.Add(-(keepOld - time.Minute)).Unix(), 656 CreatedAt: now.Add(-(time.Hour + time.Second)).Unix(), 657 }, 658 Kept: true, 659 }, 660 { 661 // should be removed, since it has a newer edition and not be used recently. 662 Cache: &Cache{ 663 Key: "test_key_1", 664 Version: "test_version", 665 Complete: true, 666 UsedAt: now.Add(-(keepOld + time.Second)).Unix(), 667 CreatedAt: now.Add(-(time.Hour + time.Second)).Unix(), 668 }, 669 Kept: false, 670 }, 671 } 672 673 db, err := handler.openDB() 674 require.NoError(t, err) 675 for _, c := range cases { 676 require.NoError(t, insertCache(db, c.Cache)) 677 } 678 require.NoError(t, db.Close()) 679 680 handler.gcAt = time.Time{} // ensure gcCache will not skip 681 handler.gcCache() 682 683 db, err = handler.openDB() 684 require.NoError(t, err) 685 for i, v := range cases { 686 t.Run(fmt.Sprintf("%d_%s", i, v.Cache.Key), func(t *testing.T) { 687 cache := &Cache{} 688 err = db.Get(v.Cache.ID, cache) 689 if v.Kept { 690 assert.NoError(t, err) 691 } else { 692 assert.ErrorIs(t, err, bolthold.ErrNotFound) 693 } 694 }) 695 } 696 require.NoError(t, db.Close()) 697 }