You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

consistency.go 8.1KB

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