|
15 | 15 | package project |
16 | 16 |
|
17 | 17 | import ( |
| 18 | + "bytes" |
| 19 | + "encoding/json" |
18 | 20 | "fmt" |
| 21 | + "io" |
| 22 | + "os" |
19 | 23 | "reflect" |
20 | 24 | "runtime" |
| 25 | + "strings" |
21 | 26 | "testing" |
22 | 27 |
|
23 | 28 | "github.com/goharbor/go-client/pkg/sdk/v2.0/client/project" |
24 | 29 | "github.com/goharbor/go-client/pkg/sdk/v2.0/models" |
25 | 30 | "github.com/goharbor/harbor-cli/pkg/api" |
| 31 | + log "github.com/sirupsen/logrus" |
| 32 | + "github.com/spf13/viper" |
26 | 33 | "github.com/stretchr/testify/assert" |
| 34 | + "go.yaml.in/yaml/v4" |
27 | 35 | ) |
28 | 36 |
|
29 | | -// api.ListProject, api.ListAllProjects |
30 | | -// -> take care of public and private |
31 | | -// -> project store and return projects according to pagination similar to user pagination, |
| 37 | +func captureOutput(f func() error) (string, error) { |
| 38 | + origStdout := os.Stdout |
| 39 | + defer func() { os.Stdout = origStdout }() |
| 40 | + r, w, err := os.Pipe() |
| 41 | + if err != nil { |
| 42 | + return "", err |
| 43 | + } |
| 44 | + defer func() { |
| 45 | + _ = w.Close() |
| 46 | + _ = r.Close() |
| 47 | + }() |
| 48 | + os.Stdout = w |
| 49 | + if err := f(); err != nil { |
| 50 | + return "", err |
| 51 | + } |
| 52 | + if err := w.Close(); err != nil { |
| 53 | + return "", err |
| 54 | + } |
| 55 | + var buf bytes.Buffer |
| 56 | + if _, err := io.Copy(&buf, r); err != nil { |
| 57 | + return "", err |
| 58 | + } |
| 59 | + return buf.String(), nil |
| 60 | +} |
| 61 | + |
32 | 62 | type MockProjectLister struct { |
33 | 63 | projectsCnt int |
34 | 64 | projects []*models.Project |
@@ -385,3 +415,194 @@ func TestFetchProjects(t *testing.T) { |
385 | 415 | }) |
386 | 416 | } |
387 | 417 | } |
| 418 | +func TestPrintProjects(t *testing.T) { |
| 419 | + var logBuf bytes.Buffer |
| 420 | + log.SetOutput(&logBuf) |
| 421 | + defer log.SetOutput(os.Stderr) |
| 422 | + |
| 423 | + testProjects := func() []*models.Project { |
| 424 | + return []*models.Project{ |
| 425 | + { |
| 426 | + ProjectID: 1, |
| 427 | + Name: "testProject1", |
| 428 | + RegistryID: 0, |
| 429 | + RepoCount: 5, |
| 430 | + Metadata: &models.ProjectMetadata{ |
| 431 | + Public: "true", |
| 432 | + }, |
| 433 | + }, |
| 434 | + { |
| 435 | + ProjectID: 2, |
| 436 | + Name: "testProject2", |
| 437 | + RegistryID: 10, |
| 438 | + RepoCount: 3, |
| 439 | + Metadata: &models.ProjectMetadata{ |
| 440 | + Public: "false", |
| 441 | + }, |
| 442 | + }, |
| 443 | + { |
| 444 | + ProjectID: 3, |
| 445 | + Name: "testProject3", |
| 446 | + RegistryID: 0, |
| 447 | + RepoCount: 0, |
| 448 | + Metadata: &models.ProjectMetadata{ |
| 449 | + Public: "false", |
| 450 | + }, |
| 451 | + }, |
| 452 | + } |
| 453 | + } |
| 454 | + tests := []struct { |
| 455 | + name string |
| 456 | + setup func() []*models.Project |
| 457 | + outputFormat string |
| 458 | + }{ |
| 459 | + { |
| 460 | + name: "Number of projects not zero and output format is json", |
| 461 | + setup: func() []*models.Project { |
| 462 | + return testProjects() |
| 463 | + }, |
| 464 | + outputFormat: "json", |
| 465 | + }, |
| 466 | + { |
| 467 | + name: "Number of projects not zero and output format yaml", |
| 468 | + setup: func() []*models.Project { |
| 469 | + return testProjects() |
| 470 | + }, |
| 471 | + outputFormat: "yaml", |
| 472 | + }, |
| 473 | + { |
| 474 | + name: "Number of projects not zero and output format default", |
| 475 | + setup: func() []*models.Project { |
| 476 | + return testProjects() |
| 477 | + }, |
| 478 | + outputFormat: "", |
| 479 | + }, |
| 480 | + { |
| 481 | + name: "Number of projects is zero", |
| 482 | + setup: func() []*models.Project { |
| 483 | + return []*models.Project{} |
| 484 | + }, |
| 485 | + outputFormat: "default", |
| 486 | + }, |
| 487 | + } |
| 488 | + for _, tt := range tests { |
| 489 | + t.Run(tt.name, func(t *testing.T) { |
| 490 | + allProjects := tt.setup() |
| 491 | + |
| 492 | + logBuf.Reset() |
| 493 | + |
| 494 | + originalFormatFlag := viper.GetString("output-format") |
| 495 | + viper.Set("output-format", tt.outputFormat) |
| 496 | + defer viper.Set("output-format", originalFormatFlag) |
| 497 | + |
| 498 | + output, err := captureOutput(func() error { |
| 499 | + return PrintProjects(allProjects) |
| 500 | + }) |
| 501 | + if err != nil { |
| 502 | + t.Fatalf("PrintProjects() returned error: %v", err) |
| 503 | + } |
| 504 | + |
| 505 | + switch { |
| 506 | + case len(allProjects) == 0: |
| 507 | + if !strings.Contains(logBuf.String(), "No projects found") { |
| 508 | + t.Errorf(`Expected logs to contain "No projects found" but got: %s`, logBuf.String()) |
| 509 | + } |
| 510 | + case tt.outputFormat == "json": |
| 511 | + if len(output) == 0 { |
| 512 | + t.Fatal("Expected JSON output, but output was empty") |
| 513 | + } |
| 514 | + var decoded []*models.Project |
| 515 | + if err := json.Unmarshal([]byte(output), &decoded); err != nil { |
| 516 | + t.Fatalf("Output is not valid JSON: %v. Output:\n%s", err, output) |
| 517 | + } |
| 518 | + if len(decoded) != len(allProjects) { |
| 519 | + t.Errorf("Expected %d projects in JSON, got %d", len(allProjects), len(decoded)) |
| 520 | + } |
| 521 | + if len(decoded) > 0 { |
| 522 | + if decoded[0].Name != allProjects[0].Name { |
| 523 | + t.Errorf("Expected name '%s', got '%s'", allProjects[0].Name, decoded[0].Name) |
| 524 | + } |
| 525 | + if decoded[0].ProjectID != allProjects[0].ProjectID { |
| 526 | + t.Errorf("Expected ProjectID %d, got %d", allProjects[0].ProjectID, decoded[0].ProjectID) |
| 527 | + } |
| 528 | + } |
| 529 | + case tt.outputFormat == "yaml": |
| 530 | + if len(output) == 0 { |
| 531 | + t.Fatal("Expected YAML output, but output was empty") |
| 532 | + } |
| 533 | + var decoded []*models.Project |
| 534 | + if err := yaml.Unmarshal([]byte(output), &decoded); err != nil { |
| 535 | + t.Fatalf("Output is not valid YAML: %v. Output:\n%s", err, output) |
| 536 | + } |
| 537 | + if len(decoded) != len(allProjects) { |
| 538 | + t.Errorf("Expected %d projects in YAML, got %d", len(allProjects), len(decoded)) |
| 539 | + } |
| 540 | + if len(decoded) > 0 { |
| 541 | + if decoded[0].Name != allProjects[0].Name { |
| 542 | + t.Errorf("Expected name '%s', got '%s'", allProjects[0].Name, decoded[0].Name) |
| 543 | + } |
| 544 | + if decoded[0].ProjectID != allProjects[0].ProjectID { |
| 545 | + t.Errorf("Expected ProjectID %d, got %d", allProjects[0].ProjectID, decoded[0].ProjectID) |
| 546 | + } |
| 547 | + } |
| 548 | + default: |
| 549 | + if len(output) == 0 { |
| 550 | + t.Fatal("Expected TUI table output, but output was empty") |
| 551 | + } |
| 552 | + if !strings.Contains(output, "ID") || !strings.Contains(output, "Project Name") || !strings.Contains(output, "Access Level") { |
| 553 | + t.Error("Expected table output to contain headers 'ID', 'Project Name' and 'Access Level' among other headers") |
| 554 | + } |
| 555 | + if !strings.Contains(output, "testProject1") { |
| 556 | + t.Errorf("Expected table to contain project name 'testProject1'") |
| 557 | + } |
| 558 | + } |
| 559 | + }) |
| 560 | + } |
| 561 | +} |
| 562 | +func TestListProjectCommand(t *testing.T) { |
| 563 | + cmd := ListProjectCommand() |
| 564 | + |
| 565 | + assert.Equal(t, "list", cmd.Use, "Expected command use to be 'list'") |
| 566 | + assert.NotEmpty(t, cmd.Short, "Expected a short description for the command") |
| 567 | + assert.NotNil(t, cmd.Args, "Expected Args validator to be set") |
| 568 | + |
| 569 | + flags := cmd.Flags() |
| 570 | + |
| 571 | + nameFlag := flags.Lookup("name") |
| 572 | + assert.NotNil(t, nameFlag, "Expected 'name' flag to exist") |
| 573 | + assert.Equal(t, "", nameFlag.DefValue, "Expected 'name' flag default value to be empty string") |
| 574 | + |
| 575 | + pageFlag := flags.Lookup("page") |
| 576 | + assert.NotNil(t, pageFlag, "Expected 'page' flag to exist") |
| 577 | + assert.Equal(t, "1", pageFlag.DefValue, "Expected 'page' flag default value to be 1") |
| 578 | + |
| 579 | + pageSizeFlag := flags.Lookup("page-size") |
| 580 | + assert.NotNil(t, pageSizeFlag, "Expected 'page-size' flag to exist") |
| 581 | + assert.Equal(t, "0", pageSizeFlag.DefValue, "Expected 'page-size' flag default value to be 0") |
| 582 | + |
| 583 | + privateFlag := flags.Lookup("private") |
| 584 | + assert.NotNil(t, privateFlag, "Expected 'private' flag to exist") |
| 585 | + assert.Equal(t, "false", privateFlag.DefValue, "Expected 'private' flag default value to be false") |
| 586 | + |
| 587 | + publicFlag := flags.Lookup("public") |
| 588 | + assert.NotNil(t, publicFlag, "Expected 'public' flag to exist") |
| 589 | + assert.Equal(t, "false", publicFlag.DefValue, "Expected 'public' flag default value to be false") |
| 590 | + |
| 591 | + sortFlag := flags.Lookup("sort") |
| 592 | + assert.NotNil(t, sortFlag, "Expected 'sort' flag to exist") |
| 593 | + assert.Equal(t, "", sortFlag.DefValue, "Expected 'sort' flag default value to be empty string") |
| 594 | + |
| 595 | + fuzzyFlag := flags.Lookup("fuzzy") |
| 596 | + assert.NotNil(t, fuzzyFlag, "Expected 'fuzzy' flag to exist") |
| 597 | + assert.Equal(t, "[]", fuzzyFlag.DefValue, "Expected 'fuzzy' flag default value to be empty slice") |
| 598 | + |
| 599 | + matchFlag := flags.Lookup("match") |
| 600 | + assert.NotNil(t, matchFlag, "Expected 'match' flag to exist") |
| 601 | + assert.Equal(t, "[]", matchFlag.DefValue, "Expected 'match' flag default value to be empty slice") |
| 602 | + |
| 603 | + rangeFlag := flags.Lookup("range") |
| 604 | + assert.NotNil(t, rangeFlag, "Expected 'range' flag to exist") |
| 605 | + assert.Equal(t, "[]", rangeFlag.DefValue, "Expected 'range' flag default value to be empty slice") |
| 606 | + |
| 607 | + assert.NotNil(t, cmd.RunE, "Expected RunE to be not nil") |
| 608 | +} |
0 commit comments