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  }