github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/scm/github_test.go (about)

     1  package scm_test
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/google/go-github/v45/github"
    10  	"github.com/quickfeed/quickfeed/kit/score"
    11  	"github.com/quickfeed/quickfeed/qf"
    12  	"github.com/quickfeed/quickfeed/scm"
    13  )
    14  
    15  const (
    16  	qf101Org   = "qf101"
    17  	qf101OrdID = 77283363
    18  )
    19  
    20  // To run this test, please see instructions in the developer guide (dev.md).
    21  
    22  func TestGetOrganization(t *testing.T) {
    23  	qfTestOrg := scm.GetTestOrganization(t)
    24  	s, qfTestUser := scm.GetTestSCM(t)
    25  	org, err := s.GetOrganization(context.Background(), &scm.OrganizationOptions{
    26  		Name:     qfTestOrg,
    27  		Username: qfTestUser,
    28  	})
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  	if qfTestOrg == qf101Org {
    33  		if org.GetScmOrganizationID() != qf101OrdID {
    34  			t.Errorf("GetOrganization(%q) = %d, expected %d", qfTestOrg, org.GetScmOrganizationID(), qf101OrdID)
    35  		}
    36  	} else {
    37  		// Otherwise, we just print the organization result
    38  		t.Logf("org: %v", org)
    39  	}
    40  }
    41  
    42  // Test case for Creating new Issue on a git Repository
    43  func TestCreateIssue(t *testing.T) {
    44  	qfTestOrg := scm.GetTestOrganization(t)
    45  	s, qfTestUser := scm.GetTestSCM(t)
    46  
    47  	issue, cleanup := createIssue(t, s, qfTestOrg, qf.StudentRepoName(qfTestUser))
    48  	defer cleanup()
    49  
    50  	if !(issue.Title == "Test Issue" && issue.Body == "Test Body") {
    51  		t.Errorf("scm.TestCreateIssue: issue: %v", issue)
    52  	}
    53  }
    54  
    55  func TestGetIssue(t *testing.T) {
    56  	qfTestOrg := scm.GetTestOrganization(t)
    57  	s, qfTestUser := scm.GetTestSCM(t)
    58  
    59  	opt := &scm.RepositoryOptions{
    60  		Owner: qfTestOrg,
    61  		Path:  qf.StudentRepoName(qfTestUser),
    62  	}
    63  
    64  	wantIssue, cleanup := createIssue(t, s, opt.Owner, opt.Path)
    65  	defer cleanup()
    66  
    67  	gotIssue, err := s.GetIssue(context.Background(), opt, wantIssue.Number)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	if diff := cmp.Diff(wantIssue, gotIssue); diff != "" {
    73  		t.Errorf("scm.TestGetIssue() mismatch (-wantIssue +gotIssue):\n%s", diff)
    74  	}
    75  }
    76  
    77  // Test case for Updating existing Issue in a git Repository
    78  func TestUpdateIssue(t *testing.T) {
    79  	qfTestOrg := scm.GetTestOrganization(t)
    80  	s, qfTestUser := scm.GetTestSCM(t)
    81  
    82  	opt := &scm.IssueOptions{
    83  		Organization: qfTestOrg,
    84  		Repository:   qf.StudentRepoName(qfTestUser),
    85  		Title:        "Updated Issue",
    86  		Body:         "Updated Issue Body",
    87  	}
    88  
    89  	issue, cleanup := createIssue(t, s, opt.Organization, opt.Repository)
    90  	defer cleanup()
    91  
    92  	opt.Number = issue.Number
    93  	gotIssue, err := s.UpdateIssue(context.Background(), opt)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  
    98  	if gotIssue.Title != opt.Title || gotIssue.Body != opt.Body {
    99  		t.Errorf("scm.TestUpdateIssue() want (title: %s, body: %s), got (title: %s, body: %s)", opt.Title, opt.Body, gotIssue.Title, gotIssue.Body)
   100  	}
   101  }
   102  
   103  // This test will delete all open and closed issues for the test user and organization.
   104  // The test is skipped unless run with: SCM_TESTS=1 go test -v -run TestDeleteAllIssues
   105  func TestDeleteAllIssues(t *testing.T) {
   106  	if os.Getenv("SCM_TESTS") == "" {
   107  		t.SkipNow()
   108  	}
   109  	qfTestOrg := scm.GetTestOrganization(t)
   110  	s, qfTestUser := scm.GetTestSCM(t)
   111  
   112  	opt := &scm.RepositoryOptions{
   113  		Owner: qfTestOrg,
   114  		Path:  qf.StudentRepoName(qfTestUser),
   115  	}
   116  	if err := s.DeleteIssues(context.Background(), opt); err != nil {
   117  		t.Fatal(err)
   118  	}
   119  }
   120  
   121  // TestRequestReviewers tests the ability to request reviewers for a pull request.
   122  // It will first create a pull request, then request reviewers for it and then closes the pull request.
   123  //
   124  // Note: This test requires manual steps before execution:
   125  // 1. Create branch test-request-reviewers on the qfTestUser repo
   126  // 2. Make edits on the test-request-reviewers branch
   127  // 3. Push the changes to the qfTestUser repo
   128  //
   129  // The test is skipped unless run with: SCM_TESTS=1 go test -v -run TestRequestReviewers
   130  func TestRequestReviewers(t *testing.T) {
   131  	if os.Getenv("SCM_TESTS") == "" {
   132  		t.SkipNow()
   133  	}
   134  	qfTestOrg := scm.GetTestOrganization(t)
   135  	s, qfTestUser := scm.GetTestSCM(t)
   136  	repo := qf.StudentRepoName(qfTestUser)
   137  
   138  	testReqReviewersBranch := "test-request-reviewers"
   139  
   140  	ctx := context.Background()
   141  	pullReq, _, err := s.Client().PullRequests.Create(ctx, qfTestOrg, repo, &github.NewPullRequest{
   142  		Title: github.String("Test Request Reviewers"),
   143  		Body:  github.String("Test Request Reviewers Body"),
   144  		Head:  github.String(testReqReviewersBranch),
   145  		Base:  github.String("master"),
   146  	})
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	t.Logf("PullRequest %d opened", *pullReq.Number)
   151  
   152  	// Pick a reviewer that is not the current user (that created the PR above).
   153  	var reviewer string
   154  	for _, r := range []string{"meling", "JosteinLindhom"} {
   155  		if r != qfTestUser {
   156  			reviewer = r
   157  			break
   158  		}
   159  	}
   160  
   161  	opt := &scm.RequestReviewersOptions{
   162  		Organization: qfTestOrg,
   163  		Repository:   repo,
   164  		Number:       *pullReq.Number,
   165  		Reviewers:    []string{reviewer},
   166  	}
   167  	if err := s.RequestReviewers(ctx, opt); err != nil {
   168  		t.Fatal(err)
   169  	}
   170  	t.Logf("PullRequest %d created with reviewer %v", *pullReq.Number, reviewer)
   171  
   172  	_, _, err = s.Client().PullRequests.Edit(ctx, qfTestOrg, repo, *pullReq.Number, &github.PullRequest{
   173  		State: github.String("closed"),
   174  	})
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  	t.Logf("PullRequest %d closed", *pullReq.Number)
   179  }
   180  
   181  func TestCreateIssueComment(t *testing.T) {
   182  	qfTestOrg := scm.GetTestOrganization(t)
   183  	s, qfTestUser := scm.GetTestSCM(t)
   184  
   185  	body := "Test"
   186  	opt := &scm.IssueCommentOptions{
   187  		Organization: qfTestOrg,
   188  		Repository:   qf.StudentRepoName(qfTestUser),
   189  		Body:         body,
   190  	}
   191  
   192  	issue, cleanup := createIssue(t, s, opt.Organization, opt.Repository)
   193  	defer cleanup()
   194  
   195  	opt.Number = issue.Number
   196  	_, err := s.CreateIssueComment(context.Background(), opt)
   197  	if err != nil {
   198  		t.Fatal(err)
   199  	}
   200  }
   201  
   202  // TestFeedbackCommentFormat tests creating a feedback comment on a pull request, with the given result.
   203  // Note: Manual step required to view the resulting comment: disable the cleanup() function.
   204  // The test is skipped unless run with: SCM_TESTS=1 go test -v -run TestFeedbackCommentFormat
   205  func TestFeedbackCommentFormat(t *testing.T) {
   206  	if os.Getenv("SCM_TESTS") == "" {
   207  		t.SkipNow()
   208  	}
   209  	qfTestOrg := scm.GetTestOrganization(t)
   210  	s, qfTestUser := scm.GetTestSCM(t)
   211  
   212  	opt := &scm.IssueCommentOptions{
   213  		Organization: qfTestOrg,
   214  		Repository:   qf.StudentRepoName(qfTestUser),
   215  		Body:         "Some initial feedback",
   216  	}
   217  	// Using the IssueCommentOptions opt fields to create the issue; the opt will be used below.
   218  	issue, cleanup := createIssue(t, s, opt.Organization, opt.Repository)
   219  	defer cleanup()
   220  
   221  	opt.Number = issue.Number
   222  	// The created comment will be deleted when the parent issue is deleted.
   223  	commentID, err := s.CreateIssueComment(context.Background(), opt)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  
   228  	results := &score.Results{
   229  		Scores: []*score.Score{
   230  			{TestName: "Test1", TaskName: "1", Score: 5, MaxScore: 7, Weight: 2},
   231  			{TestName: "Test2", TaskName: "1", Score: 3, MaxScore: 9, Weight: 3},
   232  			{TestName: "Test3", TaskName: "1", Score: 8, MaxScore: 8, Weight: 5},
   233  			{TestName: "Test4", TaskName: "1", Score: 2, MaxScore: 5, Weight: 1},
   234  			{TestName: "Test5", TaskName: "1", Score: 5, MaxScore: 7, Weight: 1},
   235  			{TestName: "Test6", TaskName: "2", Score: 5, MaxScore: 7, Weight: 1},
   236  			{TestName: "Test7", TaskName: "3", Score: 5, MaxScore: 7, Weight: 1},
   237  		},
   238  	}
   239  	body := results.MarkdownComment("1", 80)
   240  	opt.CommentID = commentID
   241  	opt.Body = body
   242  	if err := s.UpdateIssueComment(context.Background(), opt); err != nil {
   243  		t.Fatal(err)
   244  	}
   245  }
   246  
   247  // This test assumes that the test organization has an empty "info" repository
   248  // and non-empty "tests" repository.
   249  func TestEmptyRepo(t *testing.T) {
   250  	qfTestOrg := scm.GetTestOrganization(t)
   251  	s, _ := scm.GetTestSCM(t)
   252  	ctx := context.Background()
   253  
   254  	tests := []struct {
   255  		name      string
   256  		opt       *scm.RepositoryOptions
   257  		wantEmpty bool
   258  	}{
   259  		{
   260  			"tests repo, assume not empty",
   261  			&scm.RepositoryOptions{
   262  				Path:  "tests",
   263  				Owner: qfTestOrg,
   264  			},
   265  			false,
   266  		},
   267  		{
   268  			"info repo, assume empty",
   269  			&scm.RepositoryOptions{
   270  				Path:  "info",
   271  				Owner: qfTestOrg,
   272  			},
   273  			true,
   274  		},
   275  		{
   276  			"non-existent repo, handle as empty",
   277  			&scm.RepositoryOptions{
   278  				Path:  "some-other-repo",
   279  				Owner: qfTestOrg,
   280  			},
   281  			true,
   282  		},
   283  	}
   284  	for _, tt := range tests {
   285  		if empty := s.RepositoryIsEmpty(ctx, tt.opt); empty != tt.wantEmpty {
   286  			t.Errorf("%s: expected empty repository: %v, got = %v, ", tt.name, tt.wantEmpty, empty)
   287  		}
   288  	}
   289  }
   290  
   291  // createIssue on the given repository; returns the issue and a cleanup function.
   292  func createIssue(t *testing.T, s scm.SCM, org, repo string) (*scm.Issue, func()) {
   293  	t.Helper()
   294  	issue, err := s.CreateIssue(context.Background(), &scm.IssueOptions{
   295  		Organization: org,
   296  		Repository:   repo,
   297  		Title:        "Test Issue",
   298  		Body:         "Test Body",
   299  	})
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	return issue, func() {
   305  		if err := s.DeleteIssue(context.Background(), &scm.RepositoryOptions{
   306  			Owner: org, Path: repo,
   307  		}, issue.Number); err != nil {
   308  			t.Fatal(err)
   309  		}
   310  	}
   311  }