github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/assignments/tasks_test.go (about)

     1  package assignments
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/quickfeed/quickfeed/database"
     8  	"github.com/quickfeed/quickfeed/internal/qtest"
     9  	"github.com/quickfeed/quickfeed/qf"
    10  	"github.com/quickfeed/quickfeed/scm"
    11  )
    12  
    13  // TestSynchronizeTasksWithIssues synchronizes tasks with issues on user repositories
    14  // on the specified test organization. The test deletes existing issues on the test
    15  // organization first, before synchronizing the tasks to issues. The test will leave
    16  // behind newly created issues on the user repositories for manual inspection.
    17  func TestSynchronizeTasksWithIssues(t *testing.T) {
    18  	db, cleanup := qtest.TestDB(t)
    19  	defer cleanup()
    20  
    21  	scmApp := scm.GetAppSCM(t)
    22  	course, assignments, repos := initDatabase(t, db, scmApp)
    23  	ctx := context.Background()
    24  
    25  	// Delete all issues on student repositories
    26  	repoFn(repos, func(repo *scm.Repository) {
    27  		if err := scmApp.DeleteIssues(ctx, &scm.RepositoryOptions{
    28  			Owner: course.ScmOrganizationName,
    29  			Path:  repo.Path,
    30  		}); err != nil {
    31  			t.Fatal(err)
    32  		}
    33  		t.Logf("Deleted issues at repo: %+v", repo.Path)
    34  	})
    35  
    36  	// Create issues on student repositories for the first assignment's tasks
    37  	first := assignments[:1]
    38  	t.Logf("Synchronizing tasks with issues for assignment %d with %d tasks", first[0].GetOrder(), len(first[0].Tasks))
    39  	if err := synchronizeTasksWithIssues(ctx, db, scmApp, course, first); err != nil {
    40  		t.Fatal(err)
    41  	}
    42  
    43  	// Check if the issues were created
    44  	repoFn(repos, func(repo *scm.Repository) {
    45  		scmIssues, err := scmApp.GetIssues(ctx, &scm.RepositoryOptions{
    46  			Owner: course.ScmOrganizationName,
    47  			Path:  repo.Path,
    48  		})
    49  		if err != nil {
    50  			t.Fatal(err)
    51  		}
    52  		issues := make(map[string]*scm.Issue)
    53  		for _, issue := range scmIssues {
    54  			t.Logf("Found issue (%s): %s", repo.Path, issue.Title)
    55  			issues[issue.Title] = issue
    56  		}
    57  		for _, task := range first[0].Tasks {
    58  			if _, ok := issues[task.Title]; !ok {
    59  				t.Errorf("task.Title = %s not found in repo %s", task.Title, repo.Path)
    60  			}
    61  		}
    62  	})
    63  
    64  	// Create issues on student repositories for the second assignment's tasks
    65  	second := assignments[1:]
    66  	t.Logf("Synchronizing tasks with issues for assignment %d with %d tasks", second[0].GetOrder(), len(second[0].Tasks))
    67  	if err := synchronizeTasksWithIssues(ctx, db, scmApp, course, second); err != nil {
    68  		t.Fatal(err)
    69  	}
    70  
    71  	// Check if the issues were created
    72  	repoFn(repos, func(repo *scm.Repository) {
    73  		scmIssues, err := scmApp.GetIssues(ctx, &scm.RepositoryOptions{
    74  			Owner: course.ScmOrganizationName,
    75  			Path:  repo.Path,
    76  		})
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  		issues := make(map[string]*scm.Issue)
    81  		for _, issue := range scmIssues {
    82  			t.Logf("Found issue (%s): %s", repo.Path, issue.Title)
    83  			issues[issue.Title] = issue
    84  		}
    85  		for _, task := range second[0].Tasks {
    86  			if _, ok := issues[task.Title]; !ok {
    87  				t.Errorf("task.Title = %s not found in repo %s", task.Title, repo.Path)
    88  			}
    89  		}
    90  	})
    91  }
    92  
    93  func repoFn(repos []*scm.Repository, fn func(repo *scm.Repository)) {
    94  	for _, repo := range repos {
    95  		if qf.RepoType(repo.Path).IsCourseRepo() {
    96  			continue
    97  		}
    98  		fn(repo)
    99  	}
   100  }
   101  
   102  // initDatabase creates initial data-records based on the QF_TEST_ORG organization.
   103  // This function is used for testing task and pull request related functionality.
   104  func initDatabase(t *testing.T, db database.Database, sc scm.SCM) (*qf.Course, []*qf.Assignment, []*scm.Repository) {
   105  	t.Helper()
   106  
   107  	qfTestOrg := scm.GetTestOrganization(t)
   108  	ctx := context.Background()
   109  	org, err := sc.GetOrganization(ctx, &scm.OrganizationOptions{Name: qfTestOrg})
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  
   114  	course := &qf.Course{
   115  		Name:                "QuickFeed Test Course",
   116  		ScmOrganizationName: org.GetScmOrganizationName(),
   117  		ScmOrganizationID:   org.GetScmOrganizationID(),
   118  	}
   119  	admin := qtest.CreateFakeUser(t, db)
   120  	qtest.CreateCourse(t, db, admin, course)
   121  
   122  	repos, err := sc.GetRepositories(ctx, org)
   123  	if err != nil {
   124  		t.Fatal(err)
   125  	}
   126  
   127  	// Add repositories to the database
   128  	for _, scmRepo := range repos {
   129  		repo := &qf.Repository{
   130  			ScmRepositoryID:   scmRepo.ID,
   131  			ScmOrganizationID: org.GetScmOrganizationID(),
   132  			HTMLURL:           scmRepo.HTMLURL,
   133  			RepoType:          qf.RepoType(scmRepo.Path),
   134  		}
   135  		if repo.IsUserRepo() {
   136  			user := qtest.CreateFakeUser(t, db)
   137  			qtest.EnrollStudent(t, db, user, course)
   138  			group := &qf.Group{
   139  				Name:     repo.UserName(),
   140  				CourseID: course.GetID(),
   141  				Users:    []*qf.User{user},
   142  			}
   143  			if err := db.CreateGroup(group); err != nil {
   144  				t.Fatal(err)
   145  			}
   146  			// For testing purposes, assume all student repositories are group repositories
   147  			// since tasks and pull requests are only supported for groups anyway.
   148  			repo.RepoType = qf.Repository_GROUP
   149  			repo.GroupID = group.GetID()
   150  		}
   151  		t.Logf("Creating repo in database: %v", scmRepo.Path)
   152  		if err = db.CreateRepository(repo); err != nil {
   153  			t.Fatal(err)
   154  		}
   155  	}
   156  
   157  	assignments := []*qf.Assignment{
   158  		{
   159  			CourseID:    course.GetID(),
   160  			Name:        "lab1",
   161  			Deadline:    qtest.Timestamp(t, "2022-12-01T19:00:00"),
   162  			AutoApprove: false,
   163  			Order:       1,
   164  			IsGroupLab:  false,
   165  			Tasks: []*qf.Task{
   166  				{Title: "Fibonacci", Name: "fib", AssignmentOrder: 1, Body: "Implement fibonacci"},
   167  				{Title: "Lucas Numbers", Name: "luc", AssignmentOrder: 1, Body: "Implement lucas numbers"},
   168  			},
   169  		},
   170  		{
   171  			CourseID:    course.GetID(),
   172  			Name:        "lab2",
   173  			Deadline:    qtest.Timestamp(t, "2022-12-12T19:00:00"),
   174  			AutoApprove: false,
   175  			Order:       2,
   176  			IsGroupLab:  false,
   177  			Tasks: []*qf.Task{
   178  				{Title: "Addition", Name: "add", AssignmentOrder: 2, Body: "Implement addition"},
   179  				{Title: "Subtraction", Name: "sub", AssignmentOrder: 2, Body: "Implement subtraction"},
   180  				{Title: "Multiplication", Name: "mul", AssignmentOrder: 2, Body: "Implement multiplication"},
   181  			},
   182  		},
   183  	}
   184  	for _, assignment := range assignments {
   185  		if err := db.CreateAssignment(assignment); err != nil {
   186  			t.Fatal(err)
   187  		}
   188  	}
   189  	return course, assignments, repos
   190  }