code.gitea.io/gitea@v1.21.7/models/unittest/consistency.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package unittest 5 6 import ( 7 "reflect" 8 "strconv" 9 "strings" 10 11 "code.gitea.io/gitea/models/db" 12 13 "github.com/stretchr/testify/assert" 14 "xorm.io/builder" 15 ) 16 17 const ( 18 // these const values are copied from `models` package to prevent from cycle-import 19 modelsUserTypeOrganization = 1 20 modelsRepoWatchModeDont = 2 21 modelsCommentTypeComment = 0 22 ) 23 24 var consistencyCheckMap = make(map[string]func(t assert.TestingT, bean any)) 25 26 // CheckConsistencyFor test that all matching database entries are consistent 27 func CheckConsistencyFor(t assert.TestingT, beansToCheck ...any) { 28 for _, bean := range beansToCheck { 29 sliceType := reflect.SliceOf(reflect.TypeOf(bean)) 30 sliceValue := reflect.MakeSlice(sliceType, 0, 10) 31 32 ptrToSliceValue := reflect.New(sliceType) 33 ptrToSliceValue.Elem().Set(sliceValue) 34 35 assert.NoError(t, db.GetEngine(db.DefaultContext).Table(bean).Find(ptrToSliceValue.Interface())) 36 sliceValue = ptrToSliceValue.Elem() 37 38 for i := 0; i < sliceValue.Len(); i++ { 39 entity := sliceValue.Index(i).Interface() 40 checkForConsistency(t, entity) 41 } 42 } 43 } 44 45 func checkForConsistency(t assert.TestingT, bean any) { 46 tb, err := db.TableInfo(bean) 47 assert.NoError(t, err) 48 f := consistencyCheckMap[tb.Name] 49 if f == nil { 50 assert.Fail(t, "unknown bean type: %#v", bean) 51 return 52 } 53 f(t, bean) 54 } 55 56 func init() { 57 parseBool := func(v string) bool { 58 b, _ := strconv.ParseBool(v) 59 return b 60 } 61 parseInt := func(v string) int { 62 i, _ := strconv.Atoi(v) 63 return i 64 } 65 66 checkForUserConsistency := func(t assert.TestingT, bean any) { 67 user := reflectionWrap(bean) 68 AssertCountByCond(t, "repository", builder.Eq{"owner_id": user.int("ID")}, user.int("NumRepos")) 69 AssertCountByCond(t, "star", builder.Eq{"uid": user.int("ID")}, user.int("NumStars")) 70 AssertCountByCond(t, "org_user", builder.Eq{"org_id": user.int("ID")}, user.int("NumMembers")) 71 AssertCountByCond(t, "team", builder.Eq{"org_id": user.int("ID")}, user.int("NumTeams")) 72 AssertCountByCond(t, "follow", builder.Eq{"user_id": user.int("ID")}, user.int("NumFollowing")) 73 AssertCountByCond(t, "follow", builder.Eq{"follow_id": user.int("ID")}, user.int("NumFollowers")) 74 if user.int("Type") != modelsUserTypeOrganization { 75 assert.EqualValues(t, 0, user.int("NumMembers"), "Unexpected number of members for user id: %d", user.int("ID")) 76 assert.EqualValues(t, 0, user.int("NumTeams"), "Unexpected number of teams for user id: %d", user.int("ID")) 77 } 78 } 79 80 checkForRepoConsistency := func(t assert.TestingT, bean any) { 81 repo := reflectionWrap(bean) 82 assert.Equal(t, repo.str("LowerName"), strings.ToLower(repo.str("Name")), "repo: %+v", repo) 83 AssertCountByCond(t, "star", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumStars")) 84 AssertCountByCond(t, "milestone", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumMilestones")) 85 AssertCountByCond(t, "repository", builder.Eq{"fork_id": repo.int("ID")}, repo.int("NumForks")) 86 if repo.bool("IsFork") { 87 AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": repo.int("ForkID")}) 88 } 89 90 actual := GetCountByCond(t, "watch", builder.Eq{"repo_id": repo.int("ID")}. 91 And(builder.Neq{"mode": modelsRepoWatchModeDont})) 92 assert.EqualValues(t, repo.int("NumWatches"), actual, 93 "Unexpected number of watches for repo id: %d", repo.int("ID")) 94 95 actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "repo_id": repo.int("ID")}) 96 assert.EqualValues(t, repo.int("NumIssues"), actual, 97 "Unexpected number of issues for repo id: %d", repo.int("ID")) 98 99 actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "is_closed": true, "repo_id": repo.int("ID")}) 100 assert.EqualValues(t, repo.int("NumClosedIssues"), actual, 101 "Unexpected number of closed issues for repo id: %d", repo.int("ID")) 102 103 actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": true, "repo_id": repo.int("ID")}) 104 assert.EqualValues(t, repo.int("NumPulls"), actual, 105 "Unexpected number of pulls for repo id: %d", repo.int("ID")) 106 107 actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": true, "is_closed": true, "repo_id": repo.int("ID")}) 108 assert.EqualValues(t, repo.int("NumClosedPulls"), actual, 109 "Unexpected number of closed pulls for repo id: %d", repo.int("ID")) 110 111 actual = GetCountByCond(t, "milestone", builder.Eq{"is_closed": true, "repo_id": repo.int("ID")}) 112 assert.EqualValues(t, repo.int("NumClosedMilestones"), actual, 113 "Unexpected number of closed milestones for repo id: %d", repo.int("ID")) 114 } 115 116 checkForIssueConsistency := func(t assert.TestingT, bean any) { 117 issue := reflectionWrap(bean) 118 typeComment := modelsCommentTypeComment 119 actual := GetCountByCond(t, "comment", builder.Eq{"`type`": typeComment, "issue_id": issue.int("ID")}) 120 assert.EqualValues(t, issue.int("NumComments"), actual, "Unexpected number of comments for issue id: %d", issue.int("ID")) 121 if issue.bool("IsPull") { 122 prRow := AssertExistsAndLoadMap(t, "pull_request", builder.Eq{"issue_id": issue.int("ID")}) 123 assert.EqualValues(t, parseInt(prRow["index"]), issue.int("Index"), "Unexpected index for issue id: %d", issue.int("ID")) 124 } 125 } 126 127 checkForPullRequestConsistency := func(t assert.TestingT, bean any) { 128 pr := reflectionWrap(bean) 129 issueRow := AssertExistsAndLoadMap(t, "issue", builder.Eq{"id": pr.int("IssueID")}) 130 assert.True(t, parseBool(issueRow["is_pull"])) 131 assert.EqualValues(t, parseInt(issueRow["index"]), pr.int("Index"), "Unexpected index for pull request id: %d", pr.int("ID")) 132 } 133 134 checkForMilestoneConsistency := func(t assert.TestingT, bean any) { 135 milestone := reflectionWrap(bean) 136 AssertCountByCond(t, "issue", builder.Eq{"milestone_id": milestone.int("ID")}, milestone.int("NumIssues")) 137 138 actual := GetCountByCond(t, "issue", builder.Eq{"is_closed": true, "milestone_id": milestone.int("ID")}) 139 assert.EqualValues(t, milestone.int("NumClosedIssues"), actual, "Unexpected number of closed issues for milestone id: %d", milestone.int("ID")) 140 141 completeness := 0 142 if milestone.int("NumIssues") > 0 { 143 completeness = milestone.int("NumClosedIssues") * 100 / milestone.int("NumIssues") 144 } 145 assert.Equal(t, completeness, milestone.int("Completeness")) 146 } 147 148 checkForLabelConsistency := func(t assert.TestingT, bean any) { 149 label := reflectionWrap(bean) 150 issueLabels, err := db.GetEngine(db.DefaultContext).Table("issue_label"). 151 Where(builder.Eq{"label_id": label.int("ID")}). 152 Query() 153 assert.NoError(t, err) 154 155 assert.Len(t, issueLabels, label.int("NumIssues"), "Unexpected number of issue for label id: %d", label.int("ID")) 156 157 issueIDs := make([]int, len(issueLabels)) 158 for i, issueLabel := range issueLabels { 159 issueIDs[i], _ = strconv.Atoi(string(issueLabel["issue_id"])) 160 } 161 162 expected := int64(0) 163 if len(issueIDs) > 0 { 164 expected = GetCountByCond(t, "issue", builder.In("id", issueIDs).And(builder.Eq{"is_closed": true})) 165 } 166 assert.EqualValues(t, expected, label.int("NumClosedIssues"), "Unexpected number of closed issues for label id: %d", label.int("ID")) 167 } 168 169 checkForTeamConsistency := func(t assert.TestingT, bean any) { 170 team := reflectionWrap(bean) 171 AssertCountByCond(t, "team_user", builder.Eq{"team_id": team.int("ID")}, team.int("NumMembers")) 172 AssertCountByCond(t, "team_repo", builder.Eq{"team_id": team.int("ID")}, team.int("NumRepos")) 173 } 174 175 checkForActionConsistency := func(t assert.TestingT, bean any) { 176 action := reflectionWrap(bean) 177 if action.int("RepoID") != 1700 { // dangling intentional 178 repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")}) 179 assert.Equal(t, parseBool(repoRow["is_private"]), action.bool("IsPrivate"), "Unexpected is_private field for action id: %d", action.int("ID")) 180 } 181 } 182 183 consistencyCheckMap["user"] = checkForUserConsistency 184 consistencyCheckMap["repository"] = checkForRepoConsistency 185 consistencyCheckMap["issue"] = checkForIssueConsistency 186 consistencyCheckMap["pull_request"] = checkForPullRequestConsistency 187 consistencyCheckMap["milestone"] = checkForMilestoneConsistency 188 consistencyCheckMap["label"] = checkForLabelConsistency 189 consistencyCheckMap["team"] = checkForTeamConsistency 190 consistencyCheckMap["action"] = checkForActionConsistency 191 }