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 }