code.gitea.io/gitea@v1.22.3/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.FailNow(t, "unknown bean type: %#v", bean)
    51  	}
    52  	f(t, bean)
    53  }
    54  
    55  func init() {
    56  	parseBool := func(v string) bool {
    57  		b, _ := strconv.ParseBool(v)
    58  		return b
    59  	}
    60  	parseInt := func(v string) int {
    61  		i, _ := strconv.Atoi(v)
    62  		return i
    63  	}
    64  
    65  	checkForUserConsistency := func(t assert.TestingT, bean any) {
    66  		user := reflectionWrap(bean)
    67  		AssertCountByCond(t, "repository", builder.Eq{"owner_id": user.int("ID")}, user.int("NumRepos"))
    68  		AssertCountByCond(t, "star", builder.Eq{"uid": user.int("ID")}, user.int("NumStars"))
    69  		AssertCountByCond(t, "org_user", builder.Eq{"org_id": user.int("ID")}, user.int("NumMembers"))
    70  		AssertCountByCond(t, "team", builder.Eq{"org_id": user.int("ID")}, user.int("NumTeams"))
    71  		AssertCountByCond(t, "follow", builder.Eq{"user_id": user.int("ID")}, user.int("NumFollowing"))
    72  		AssertCountByCond(t, "follow", builder.Eq{"follow_id": user.int("ID")}, user.int("NumFollowers"))
    73  		if user.int("Type") != modelsUserTypeOrganization {
    74  			assert.EqualValues(t, 0, user.int("NumMembers"), "Unexpected number of members for user id: %d", user.int("ID"))
    75  			assert.EqualValues(t, 0, user.int("NumTeams"), "Unexpected number of teams for user id: %d", user.int("ID"))
    76  		}
    77  	}
    78  
    79  	checkForRepoConsistency := func(t assert.TestingT, bean any) {
    80  		repo := reflectionWrap(bean)
    81  		assert.Equal(t, repo.str("LowerName"), strings.ToLower(repo.str("Name")), "repo: %+v", repo)
    82  		AssertCountByCond(t, "star", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumStars"))
    83  		AssertCountByCond(t, "milestone", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumMilestones"))
    84  		AssertCountByCond(t, "repository", builder.Eq{"fork_id": repo.int("ID")}, repo.int("NumForks"))
    85  		if repo.bool("IsFork") {
    86  			AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": repo.int("ForkID")})
    87  		}
    88  
    89  		actual := GetCountByCond(t, "watch", builder.Eq{"repo_id": repo.int("ID")}.
    90  			And(builder.Neq{"mode": modelsRepoWatchModeDont}))
    91  		assert.EqualValues(t, repo.int("NumWatches"), actual,
    92  			"Unexpected number of watches for repo id: %d", repo.int("ID"))
    93  
    94  		actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "repo_id": repo.int("ID")})
    95  		assert.EqualValues(t, repo.int("NumIssues"), actual,
    96  			"Unexpected number of issues for repo id: %d", repo.int("ID"))
    97  
    98  		actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "is_closed": true, "repo_id": repo.int("ID")})
    99  		assert.EqualValues(t, repo.int("NumClosedIssues"), actual,
   100  			"Unexpected number of closed issues for repo id: %d", repo.int("ID"))
   101  
   102  		actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": true, "repo_id": repo.int("ID")})
   103  		assert.EqualValues(t, repo.int("NumPulls"), actual,
   104  			"Unexpected number of pulls for repo id: %d", repo.int("ID"))
   105  
   106  		actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": true, "is_closed": true, "repo_id": repo.int("ID")})
   107  		assert.EqualValues(t, repo.int("NumClosedPulls"), actual,
   108  			"Unexpected number of closed pulls for repo id: %d", repo.int("ID"))
   109  
   110  		actual = GetCountByCond(t, "milestone", builder.Eq{"is_closed": true, "repo_id": repo.int("ID")})
   111  		assert.EqualValues(t, repo.int("NumClosedMilestones"), actual,
   112  			"Unexpected number of closed milestones for repo id: %d", repo.int("ID"))
   113  	}
   114  
   115  	checkForIssueConsistency := func(t assert.TestingT, bean any) {
   116  		issue := reflectionWrap(bean)
   117  		typeComment := modelsCommentTypeComment
   118  		actual := GetCountByCond(t, "comment", builder.Eq{"`type`": typeComment, "issue_id": issue.int("ID")})
   119  		assert.EqualValues(t, issue.int("NumComments"), actual, "Unexpected number of comments for issue id: %d", issue.int("ID"))
   120  		if issue.bool("IsPull") {
   121  			prRow := AssertExistsAndLoadMap(t, "pull_request", builder.Eq{"issue_id": issue.int("ID")})
   122  			assert.EqualValues(t, parseInt(prRow["index"]), issue.int("Index"), "Unexpected index for issue id: %d", issue.int("ID"))
   123  		}
   124  	}
   125  
   126  	checkForPullRequestConsistency := func(t assert.TestingT, bean any) {
   127  		pr := reflectionWrap(bean)
   128  		issueRow := AssertExistsAndLoadMap(t, "issue", builder.Eq{"id": pr.int("IssueID")})
   129  		assert.True(t, parseBool(issueRow["is_pull"]))
   130  		assert.EqualValues(t, parseInt(issueRow["index"]), pr.int("Index"), "Unexpected index for pull request id: %d", pr.int("ID"))
   131  	}
   132  
   133  	checkForMilestoneConsistency := func(t assert.TestingT, bean any) {
   134  		milestone := reflectionWrap(bean)
   135  		AssertCountByCond(t, "issue", builder.Eq{"milestone_id": milestone.int("ID")}, milestone.int("NumIssues"))
   136  
   137  		actual := GetCountByCond(t, "issue", builder.Eq{"is_closed": true, "milestone_id": milestone.int("ID")})
   138  		assert.EqualValues(t, milestone.int("NumClosedIssues"), actual, "Unexpected number of closed issues for milestone id: %d", milestone.int("ID"))
   139  
   140  		completeness := 0
   141  		if milestone.int("NumIssues") > 0 {
   142  			completeness = milestone.int("NumClosedIssues") * 100 / milestone.int("NumIssues")
   143  		}
   144  		assert.Equal(t, completeness, milestone.int("Completeness"))
   145  	}
   146  
   147  	checkForLabelConsistency := func(t assert.TestingT, bean any) {
   148  		label := reflectionWrap(bean)
   149  		issueLabels, err := db.GetEngine(db.DefaultContext).Table("issue_label").
   150  			Where(builder.Eq{"label_id": label.int("ID")}).
   151  			Query()
   152  		assert.NoError(t, err)
   153  
   154  		assert.Len(t, issueLabels, label.int("NumIssues"), "Unexpected number of issue for label id: %d", label.int("ID"))
   155  
   156  		issueIDs := make([]int, len(issueLabels))
   157  		for i, issueLabel := range issueLabels {
   158  			issueIDs[i], _ = strconv.Atoi(string(issueLabel["issue_id"]))
   159  		}
   160  
   161  		expected := int64(0)
   162  		if len(issueIDs) > 0 {
   163  			expected = GetCountByCond(t, "issue", builder.In("id", issueIDs).And(builder.Eq{"is_closed": true}))
   164  		}
   165  		assert.EqualValues(t, expected, label.int("NumClosedIssues"), "Unexpected number of closed issues for label id: %d", label.int("ID"))
   166  	}
   167  
   168  	checkForTeamConsistency := func(t assert.TestingT, bean any) {
   169  		team := reflectionWrap(bean)
   170  		AssertCountByCond(t, "team_user", builder.Eq{"team_id": team.int("ID")}, team.int("NumMembers"))
   171  		AssertCountByCond(t, "team_repo", builder.Eq{"team_id": team.int("ID")}, team.int("NumRepos"))
   172  	}
   173  
   174  	checkForActionConsistency := func(t assert.TestingT, bean any) {
   175  		action := reflectionWrap(bean)
   176  		if action.int("RepoID") != 1700 { // dangling intentional
   177  			repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")})
   178  			assert.Equal(t, parseBool(repoRow["is_private"]), action.bool("IsPrivate"), "Unexpected is_private field for action id: %d", action.int("ID"))
   179  		}
   180  	}
   181  
   182  	consistencyCheckMap["user"] = checkForUserConsistency
   183  	consistencyCheckMap["repository"] = checkForRepoConsistency
   184  	consistencyCheckMap["issue"] = checkForIssueConsistency
   185  	consistencyCheckMap["pull_request"] = checkForPullRequestConsistency
   186  	consistencyCheckMap["milestone"] = checkForMilestoneConsistency
   187  	consistencyCheckMap["label"] = checkForLabelConsistency
   188  	consistencyCheckMap["team"] = checkForTeamConsistency
   189  	consistencyCheckMap["action"] = checkForActionConsistency
   190  }