github.com/apptainer/singularity@v3.1.1+incompatible/pkg/client/library/api_test.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package client 7 8 import ( 9 "encoding/json" 10 "net/http" 11 "net/http/httptest" 12 "os" 13 "reflect" 14 "testing" 15 16 "github.com/globalsign/mgo/bson" 17 "github.com/sylabs/singularity/internal/pkg/test" 18 useragent "github.com/sylabs/singularity/pkg/util/user-agent" 19 ) 20 21 const testToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" 22 23 var ( 24 testEntity = Entity{ 25 ID: bson.NewObjectId(), 26 Name: "test-user", 27 Description: "A test user", 28 } 29 30 testCollection = Collection{ 31 ID: bson.NewObjectId(), 32 Name: "test-collection", 33 Description: "A test collection", 34 Entity: testEntity.ID, 35 EntityName: testEntity.Name, 36 } 37 38 testContainer = Container{ 39 ID: bson.NewObjectId(), 40 Name: "test-container", 41 Description: "A test container", 42 Entity: testEntity.ID, 43 EntityName: testEntity.Name, 44 Collection: testEntity.ID, 45 CollectionName: testCollection.Name, 46 ImageTags: map[string]bson.ObjectId{ 47 "test-tag": bson.NewObjectId(), 48 "latest": bson.NewObjectId()}, 49 } 50 51 testImage = Image{ 52 ID: bson.NewObjectId(), 53 Hash: "sha256.e50a30881ace3d5944f5661d222db7bee5296be9e4dc7c1fcb7604bcae926e88", 54 Entity: testEntity.ID, 55 EntityName: testEntity.Name, 56 Collection: testEntity.ID, 57 CollectionName: testCollection.Name, 58 Container: testContainer.ID, 59 ContainerName: testContainer.Name, 60 } 61 62 testSearch = SearchResults{ 63 Entities: []Entity{testEntity}, 64 Collections: []Collection{testCollection}, 65 Containers: []Container{testContainer}, 66 } 67 ) 68 69 type mockService struct { 70 t *testing.T 71 code int 72 body interface{} 73 reqCallback func(*http.Request, *testing.T) 74 httpAddr string 75 httpPath string 76 httpServer *httptest.Server 77 baseURI string 78 } 79 80 func TestMain(m *testing.M) { 81 useragent.InitValue("singularity", "3.0.0-alpha.1-303-gaed8d30-dirty") 82 83 os.Exit(m.Run()) 84 } 85 86 func (m *mockService) Run() { 87 mux := http.NewServeMux() 88 mux.HandleFunc(m.httpPath, m.ServeHTTP) 89 m.httpServer = httptest.NewServer(mux) 90 m.httpAddr = m.httpServer.Listener.Addr().String() 91 m.baseURI = "http://" + m.httpAddr 92 } 93 94 func (m *mockService) Stop() { 95 m.httpServer.Close() 96 } 97 98 func (m *mockService) ServeHTTP(w http.ResponseWriter, r *http.Request) { 99 100 if m.reqCallback != nil { 101 m.reqCallback(r, m.t) 102 } 103 w.WriteHeader(m.code) 104 err := json.NewEncoder(w).Encode(&m.body) 105 if err != nil { 106 m.t.Errorf("Error encoding mock response: %v", err) 107 } 108 } 109 110 func Test_getEntity(t *testing.T) { 111 112 tests := []struct { 113 description string 114 code int 115 body interface{} 116 reqCallback func(*http.Request, *testing.T) 117 entityRef string 118 expectEntity Entity 119 expectFound bool 120 expectError bool 121 }{ 122 { 123 description: "NotFound", 124 code: http.StatusNotFound, 125 body: JSONResponse{Error: JSONError{Code: http.StatusNotFound, Status: http.StatusText(http.StatusNotFound)}}, 126 reqCallback: nil, 127 entityRef: "notthere", 128 expectEntity: Entity{}, 129 expectFound: false, 130 expectError: false, 131 }, 132 { 133 description: "Unauthorized", 134 code: http.StatusUnauthorized, 135 body: JSONResponse{Error: JSONError{Code: http.StatusUnauthorized, Status: http.StatusText(http.StatusUnauthorized)}}, 136 reqCallback: nil, 137 entityRef: "notmine", 138 expectEntity: Entity{}, 139 expectFound: false, 140 expectError: true, 141 }, 142 { 143 description: "ValidResponse", 144 code: http.StatusOK, 145 body: EntityResponse{Data: testEntity, Error: JSONError{}}, 146 reqCallback: nil, 147 entityRef: "test-user", 148 expectEntity: testEntity, 149 expectFound: true, 150 expectError: false, 151 }, 152 } 153 154 // Loop over test cases 155 for _, tt := range tests { 156 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 157 158 m := mockService{ 159 t: t, 160 code: tt.code, 161 body: tt.body, 162 reqCallback: tt.reqCallback, 163 httpPath: "/v1/entities/" + tt.entityRef, 164 } 165 166 m.Run() 167 168 entity, found, err := getEntity(m.baseURI, testToken, tt.entityRef) 169 170 if err != nil && !tt.expectError { 171 t.Errorf("Unexpected error: %v", err) 172 } 173 if err == nil && tt.expectError { 174 t.Errorf("Unexpected success. Expected error.") 175 } 176 if found != tt.expectFound { 177 t.Errorf("Got found %v - expected %v", found, tt.expectFound) 178 } 179 if !reflect.DeepEqual(entity, tt.expectEntity) { 180 t.Errorf("Got entity %v - expected %v", entity, tt.expectEntity) 181 } 182 183 m.Stop() 184 185 })) 186 187 } 188 } 189 190 func Test_getCollection(t *testing.T) { 191 192 tests := []struct { 193 description string 194 code int 195 body interface{} 196 reqCallback func(*http.Request, *testing.T) 197 collectionRef string 198 expectCollection Collection 199 expectFound bool 200 expectError bool 201 }{ 202 { 203 description: "NotFound", 204 code: http.StatusNotFound, 205 body: JSONResponse{Error: JSONError{Code: http.StatusNotFound, Status: http.StatusText(http.StatusNotFound)}}, 206 reqCallback: nil, 207 collectionRef: "notthere", 208 expectCollection: Collection{}, 209 expectFound: false, 210 expectError: false, 211 }, 212 { 213 description: "Unauthorized", 214 code: http.StatusUnauthorized, 215 body: JSONResponse{Error: JSONError{Code: http.StatusUnauthorized, Status: http.StatusText(http.StatusUnauthorized)}}, 216 reqCallback: nil, 217 collectionRef: "notmine", 218 expectCollection: Collection{}, 219 expectFound: false, 220 expectError: true, 221 }, 222 { 223 description: "ValidResponse", 224 code: http.StatusOK, 225 body: CollectionResponse{Data: testCollection, Error: JSONError{}}, 226 reqCallback: nil, 227 collectionRef: "test-entity/test-collection", 228 expectCollection: testCollection, 229 expectFound: true, 230 expectError: false, 231 }, 232 } 233 234 // Loop over test cases 235 for _, tt := range tests { 236 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 237 238 m := mockService{ 239 t: t, 240 code: tt.code, 241 body: tt.body, 242 reqCallback: tt.reqCallback, 243 httpPath: "/v1/collections/" + tt.collectionRef, 244 } 245 246 m.Run() 247 248 collection, found, err := getCollection(m.baseURI, testToken, tt.collectionRef) 249 250 if err != nil && !tt.expectError { 251 t.Errorf("Unexpected error: %v", err) 252 } 253 if err == nil && tt.expectError { 254 t.Errorf("Unexpected success. Expected error.") 255 } 256 if found != tt.expectFound { 257 t.Errorf("Got found %v - expected %v", found, tt.expectFound) 258 } 259 if !reflect.DeepEqual(collection, tt.expectCollection) { 260 t.Errorf("Got entity %v - expected %v", collection, tt.expectCollection) 261 } 262 263 m.Stop() 264 265 })) 266 267 } 268 } 269 270 func Test_getContainer(t *testing.T) { 271 272 tests := []struct { 273 description string 274 code int 275 body interface{} 276 reqCallback func(*http.Request, *testing.T) 277 containerRef string 278 expectContainer Container 279 expectFound bool 280 expectError bool 281 }{ 282 { 283 description: "NotFound", 284 code: http.StatusNotFound, 285 body: JSONResponse{Error: JSONError{Code: http.StatusNotFound, Status: http.StatusText(http.StatusNotFound)}}, 286 reqCallback: nil, 287 containerRef: "notthere", 288 expectContainer: Container{}, 289 expectFound: false, 290 expectError: false, 291 }, 292 { 293 description: "Unauthorized", 294 code: http.StatusUnauthorized, 295 body: JSONResponse{Error: JSONError{Code: http.StatusUnauthorized, Status: http.StatusText(http.StatusUnauthorized)}}, 296 reqCallback: nil, 297 containerRef: "notmine", 298 expectContainer: Container{}, 299 expectFound: false, 300 expectError: true, 301 }, 302 { 303 description: "ValidResponse", 304 code: http.StatusOK, 305 body: ContainerResponse{Data: testContainer, Error: JSONError{}}, 306 reqCallback: nil, 307 containerRef: "test-entity/test-collection/test-container", 308 expectContainer: testContainer, 309 expectFound: true, 310 expectError: false, 311 }, 312 } 313 314 // Loop over test cases 315 for _, tt := range tests { 316 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 317 318 m := mockService{ 319 t: t, 320 code: tt.code, 321 body: tt.body, 322 reqCallback: tt.reqCallback, 323 httpPath: "/v1/containers/" + tt.containerRef, 324 } 325 326 m.Run() 327 328 container, found, err := getContainer(m.baseURI, testToken, tt.containerRef) 329 330 if err != nil && !tt.expectError { 331 t.Errorf("Unexpected error: %v", err) 332 } 333 if err == nil && tt.expectError { 334 t.Errorf("Unexpected success. Expected error.") 335 } 336 if found != tt.expectFound { 337 t.Errorf("Got found %v - expected %v", found, tt.expectFound) 338 } 339 if !reflect.DeepEqual(container, tt.expectContainer) { 340 t.Errorf("Got container %v - expected %v", container, tt.expectContainer) 341 } 342 343 m.Stop() 344 345 })) 346 347 } 348 } 349 350 func Test_getImage(t *testing.T) { 351 352 tests := []struct { 353 description string 354 code int 355 body interface{} 356 reqCallback func(*http.Request, *testing.T) 357 imageRef string 358 expectImage Image 359 expectFound bool 360 expectError bool 361 }{ 362 { 363 description: "NotFound", 364 code: http.StatusNotFound, 365 body: JSONResponse{Error: JSONError{Code: http.StatusNotFound, Status: http.StatusText(http.StatusNotFound)}}, 366 reqCallback: nil, 367 imageRef: "notthere", 368 expectImage: Image{}, 369 expectFound: false, 370 expectError: false, 371 }, 372 { 373 description: "Unauthorized", 374 code: http.StatusUnauthorized, 375 body: JSONResponse{Error: JSONError{Code: http.StatusUnauthorized, Status: http.StatusText(http.StatusUnauthorized)}}, 376 reqCallback: nil, 377 imageRef: "notmine", 378 expectImage: Image{}, 379 expectFound: false, 380 expectError: true, 381 }, 382 { 383 description: "ValidResponse", 384 code: http.StatusOK, 385 body: ImageResponse{Data: testImage, Error: JSONError{}}, 386 reqCallback: nil, 387 imageRef: "test", 388 expectImage: testImage, 389 expectFound: true, 390 expectError: false, 391 }, 392 } 393 394 // Loop over test cases 395 for _, tt := range tests { 396 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 397 398 m := mockService{ 399 t: t, 400 code: tt.code, 401 body: tt.body, 402 reqCallback: tt.reqCallback, 403 httpPath: "/v1/images/" + tt.imageRef, 404 } 405 406 m.Run() 407 408 image, found, err := getImage(m.baseURI, testToken, tt.imageRef) 409 410 if err != nil && !tt.expectError { 411 t.Errorf("Unexpected error: %v", err) 412 } 413 if err == nil && tt.expectError { 414 t.Errorf("Unexpected success. Expected error.") 415 } 416 if found != tt.expectFound { 417 t.Errorf("Got found %v - expected %v", found, tt.expectFound) 418 } 419 if !reflect.DeepEqual(image, tt.expectImage) { 420 t.Errorf("Got image %v - expected %v", image, tt.expectImage) 421 } 422 423 m.Stop() 424 425 })) 426 427 } 428 } 429 430 func Test_createEntity(t *testing.T) { 431 432 tests := []struct { 433 description string 434 code int 435 body interface{} 436 reqCallback func(*http.Request, *testing.T) 437 entityRef string 438 expectEntity Entity 439 expectError bool 440 }{ 441 { 442 description: "Valid Request", 443 code: http.StatusOK, 444 body: EntityResponse{Data: testEntity, Error: JSONError{}}, 445 entityRef: "test", 446 expectEntity: testEntity, 447 expectError: false, 448 }, 449 { 450 description: "Error response", 451 code: http.StatusInternalServerError, 452 body: Entity{}, 453 entityRef: "test", 454 expectEntity: Entity{}, 455 expectError: true, 456 }, 457 } 458 459 // Loop over test cases 460 for _, tt := range tests { 461 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 462 463 m := mockService{ 464 t: t, 465 code: tt.code, 466 body: tt.body, 467 reqCallback: tt.reqCallback, 468 httpPath: "/v1/entities/", 469 } 470 471 m.Run() 472 473 entity, err := createEntity(m.baseURI, testToken, tt.entityRef) 474 475 if err != nil && !tt.expectError { 476 t.Errorf("Unexpected error: %v", err) 477 } 478 if err == nil && tt.expectError { 479 t.Errorf("Unexpected success. Expected error.") 480 } 481 if !reflect.DeepEqual(entity, tt.expectEntity) { 482 t.Errorf("Got created entity %v - expected %v", entity, tt.expectEntity) 483 } 484 485 m.Stop() 486 487 })) 488 489 } 490 } 491 492 func Test_createCollection(t *testing.T) { 493 494 tests := []struct { 495 description string 496 code int 497 body interface{} 498 reqCallback func(*http.Request, *testing.T) 499 collectionRef string 500 expectCollection Collection 501 expectError bool 502 }{ 503 { 504 description: "Valid Request", 505 code: http.StatusOK, 506 body: CollectionResponse{Data: Collection{Name: "test"}, Error: JSONError{}}, 507 collectionRef: "test", 508 expectCollection: Collection{Name: "test"}, 509 expectError: false, 510 }, 511 { 512 description: "Error response", 513 code: http.StatusInternalServerError, 514 body: Collection{}, 515 collectionRef: "test", 516 expectCollection: Collection{}, 517 expectError: true, 518 }, 519 } 520 521 // Loop over test cases 522 for _, tt := range tests { 523 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 524 525 m := mockService{ 526 t: t, 527 code: tt.code, 528 body: tt.body, 529 reqCallback: tt.reqCallback, 530 httpPath: "/v1/collections/", 531 } 532 533 m.Run() 534 535 collection, err := createCollection(m.baseURI, testToken, tt.collectionRef, bson.NewObjectId().Hex()) 536 537 if err != nil && !tt.expectError { 538 t.Errorf("Unexpected error: %v", err) 539 } 540 if err == nil && tt.expectError { 541 t.Errorf("Unexpected success. Expected error.") 542 } 543 if !reflect.DeepEqual(collection, tt.expectCollection) { 544 t.Errorf("Got created collection %v - expected %v", collection, tt.expectCollection) 545 } 546 547 m.Stop() 548 549 })) 550 551 } 552 } 553 554 func Test_createContainer(t *testing.T) { 555 556 tests := []struct { 557 description string 558 code int 559 body interface{} 560 reqCallback func(*http.Request, *testing.T) 561 containerRef string 562 expectContainer Container 563 expectError bool 564 }{ 565 { 566 description: "Valid Request", 567 code: http.StatusOK, 568 body: ContainerResponse{Data: Container{Name: "test"}, Error: JSONError{}}, 569 containerRef: "test", 570 expectContainer: Container{Name: "test"}, 571 expectError: false, 572 }, 573 { 574 description: "Error response", 575 code: http.StatusInternalServerError, 576 body: Container{}, 577 containerRef: "test", 578 expectContainer: Container{}, 579 expectError: true, 580 }, 581 } 582 583 // Loop over test cases 584 for _, tt := range tests { 585 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 586 587 m := mockService{ 588 t: t, 589 code: tt.code, 590 body: tt.body, 591 reqCallback: tt.reqCallback, 592 httpPath: "/v1/containers/", 593 } 594 595 m.Run() 596 597 container, err := createContainer(m.baseURI, testToken, tt.containerRef, bson.NewObjectId().Hex()) 598 599 if err != nil && !tt.expectError { 600 t.Errorf("Unexpected error: %v", err) 601 } 602 if err == nil && tt.expectError { 603 t.Errorf("Unexpected success. Expected error.") 604 } 605 if !reflect.DeepEqual(container, tt.expectContainer) { 606 t.Errorf("Got created collection %v - expected %v", container, tt.expectContainer) 607 } 608 609 m.Stop() 610 611 })) 612 613 } 614 } 615 616 func Test_createImage(t *testing.T) { 617 618 tests := []struct { 619 description string 620 code int 621 body interface{} 622 reqCallback func(*http.Request, *testing.T) 623 imageRef string 624 expectImage Image 625 expectError bool 626 }{ 627 { 628 description: "Valid Request", 629 code: http.StatusOK, 630 body: ImageResponse{Data: Image{Hash: "sha256.e50a30881ace3d5944f5661d222db7bee5296be9e4dc7c1fcb7604bcae926e88"}, Error: JSONError{}}, 631 imageRef: "test", 632 expectImage: Image{Hash: "sha256.e50a30881ace3d5944f5661d222db7bee5296be9e4dc7c1fcb7604bcae926e88"}, 633 expectError: false, 634 }, 635 { 636 description: "Error response", 637 code: http.StatusInternalServerError, 638 body: Image{}, 639 imageRef: "test", 640 expectImage: Image{}, 641 expectError: true, 642 }, 643 } 644 645 // Loop over test cases 646 for _, tt := range tests { 647 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 648 649 m := mockService{ 650 t: t, 651 code: tt.code, 652 body: tt.body, 653 reqCallback: tt.reqCallback, 654 httpPath: "/v1/images/", 655 } 656 657 m.Run() 658 659 image, err := createImage(m.baseURI, testToken, tt.imageRef, bson.NewObjectId().Hex(), "No Description") 660 661 if err != nil && !tt.expectError { 662 t.Errorf("Unexpected error: %v", err) 663 } 664 if err == nil && tt.expectError { 665 t.Errorf("Unexpected success. Expected error.") 666 } 667 if !reflect.DeepEqual(image, tt.expectImage) { 668 t.Errorf("Got created collection %v - expected %v", image, tt.expectImage) 669 } 670 671 m.Stop() 672 673 })) 674 675 } 676 } 677 678 func Test_setTags(t *testing.T) { 679 680 tests := []struct { 681 description string 682 code int 683 reqCallback func(*http.Request, *testing.T) 684 containerRef string 685 imageRef string 686 tags []string 687 expectError bool 688 }{ 689 { 690 description: "Valid Request", 691 code: http.StatusOK, 692 containerRef: "test", 693 imageRef: bson.NewObjectId().Hex(), 694 tags: []string{"tag1", "tag2", "tag3"}, 695 expectError: false, 696 }, 697 { 698 description: "Error response", 699 code: http.StatusInternalServerError, 700 containerRef: "test", 701 imageRef: bson.NewObjectId().Hex(), 702 tags: []string{"tag1", "tag2", "tag3"}, 703 expectError: true, 704 }, 705 } 706 707 // Loop over test cases 708 for _, tt := range tests { 709 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 710 711 m := mockService{ 712 t: t, 713 code: tt.code, 714 reqCallback: tt.reqCallback, 715 httpPath: "/v1/tags/" + tt.containerRef, 716 } 717 718 m.Run() 719 720 err := setTags(m.baseURI, testToken, tt.containerRef, tt.imageRef, tt.tags) 721 722 if err != nil && !tt.expectError { 723 t.Errorf("Unexpected error: %v", err) 724 } 725 if err == nil && tt.expectError { 726 t.Errorf("Unexpected success. Expected error.") 727 } 728 729 m.Stop() 730 731 })) 732 733 } 734 } 735 736 func Test_search(t *testing.T) { 737 tests := []struct { 738 description string 739 code int 740 body interface{} 741 reqCallback func(*http.Request, *testing.T) 742 value string 743 expectResults SearchResults 744 expectError bool 745 }{ 746 { 747 description: "ValidRequest", 748 value: "test", 749 code: http.StatusOK, 750 body: JSONResponse{Data: testSearch, Error: JSONError{}}, 751 expectResults: testSearch, 752 expectError: false, 753 }, 754 { 755 description: "InternalServerError", 756 value: "test", 757 code: http.StatusInternalServerError, 758 expectError: true, 759 }, 760 { 761 description: "BadRequest", 762 value: "test", 763 code: http.StatusBadRequest, 764 expectError: true, 765 }, 766 } 767 768 // Loop over test cases 769 for _, tt := range tests { 770 t.Run(tt.description, test.WithoutPrivilege(func(t *testing.T) { 771 772 m := mockService{ 773 t: t, 774 code: tt.code, 775 body: tt.body, 776 reqCallback: tt.reqCallback, 777 httpPath: "/v1/search", 778 } 779 780 m.Run() 781 782 results, err := search(m.baseURI, testToken, tt.value) 783 784 if err != nil && !tt.expectError { 785 t.Errorf("Unexpected error: %v", err) 786 } 787 if err == nil && tt.expectError { 788 t.Errorf("Unexpected success. Expected error.") 789 } 790 if !reflect.DeepEqual(results, tt.expectResults) { 791 t.Errorf("Got created collection %v - expected %v", results, tt.expectResults) 792 } 793 794 m.Stop() 795 796 })) 797 798 } 799 }