code.gitea.io/gitea@v1.22.3/modules/indexer/issues/internal/tests/tests.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  // This package contains tests for issues indexer modules.
     5  // All the code in this package is only used for testing.
     6  // Do not put any production code in this package to avoid it being included in the final binary.
     7  
     8  package tests
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"slices"
    14  	"testing"
    15  	"time"
    16  
    17  	"code.gitea.io/gitea/models/db"
    18  	"code.gitea.io/gitea/modules/indexer/issues/internal"
    19  	"code.gitea.io/gitea/modules/optional"
    20  	"code.gitea.io/gitea/modules/timeutil"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func TestIndexer(t *testing.T, indexer internal.Indexer) {
    27  	_, err := indexer.Init(context.Background())
    28  	require.NoError(t, err)
    29  
    30  	require.NoError(t, indexer.Ping(context.Background()))
    31  
    32  	var (
    33  		ids  []int64
    34  		data = map[int64]*internal.IndexerData{}
    35  	)
    36  	{
    37  		d := generateDefaultIndexerData()
    38  		for _, v := range d {
    39  			ids = append(ids, v.ID)
    40  			data[v.ID] = v
    41  		}
    42  		require.NoError(t, indexer.Index(context.Background(), d...))
    43  		require.NoError(t, waitData(indexer, int64(len(data))))
    44  	}
    45  
    46  	defer func() {
    47  		require.NoError(t, indexer.Delete(context.Background(), ids...))
    48  	}()
    49  
    50  	for _, c := range cases {
    51  		t.Run(c.Name, func(t *testing.T) {
    52  			if len(c.ExtraData) > 0 {
    53  				require.NoError(t, indexer.Index(context.Background(), c.ExtraData...))
    54  				for _, v := range c.ExtraData {
    55  					data[v.ID] = v
    56  				}
    57  				require.NoError(t, waitData(indexer, int64(len(data))))
    58  				defer func() {
    59  					for _, v := range c.ExtraData {
    60  						require.NoError(t, indexer.Delete(context.Background(), v.ID))
    61  						delete(data, v.ID)
    62  					}
    63  					require.NoError(t, waitData(indexer, int64(len(data))))
    64  				}()
    65  			}
    66  
    67  			result, err := indexer.Search(context.Background(), c.SearchOptions)
    68  			require.NoError(t, err)
    69  
    70  			if c.Expected != nil {
    71  				c.Expected(t, data, result)
    72  			} else {
    73  				ids := make([]int64, 0, len(result.Hits))
    74  				for _, hit := range result.Hits {
    75  					ids = append(ids, hit.ID)
    76  				}
    77  				assert.Equal(t, c.ExpectedIDs, ids)
    78  				assert.Equal(t, c.ExpectedTotal, result.Total)
    79  			}
    80  
    81  			// test counting
    82  			c.SearchOptions.Paginator = &db.ListOptions{PageSize: 0}
    83  			countResult, err := indexer.Search(context.Background(), c.SearchOptions)
    84  			require.NoError(t, err)
    85  			assert.Empty(t, countResult.Hits)
    86  			assert.Equal(t, result.Total, countResult.Total)
    87  		})
    88  	}
    89  }
    90  
    91  var cases = []*testIndexerCase{
    92  	{
    93  		Name:          "default",
    94  		SearchOptions: &internal.SearchOptions{},
    95  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
    96  			assert.Equal(t, len(data), len(result.Hits))
    97  			assert.Equal(t, len(data), int(result.Total))
    98  		},
    99  	},
   100  	{
   101  		Name: "empty",
   102  		SearchOptions: &internal.SearchOptions{
   103  			Keyword: "f1dfac73-fda6-4a6b-b8a4-2408fcb8ef69",
   104  		},
   105  		ExpectedIDs:   []int64{},
   106  		ExpectedTotal: 0,
   107  	},
   108  	{
   109  		Name: "with limit",
   110  		SearchOptions: &internal.SearchOptions{
   111  			Paginator: &db.ListOptions{
   112  				PageSize: 5,
   113  			},
   114  		},
   115  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   116  			assert.Equal(t, 5, len(result.Hits))
   117  			assert.Equal(t, len(data), int(result.Total))
   118  		},
   119  	},
   120  	{
   121  		Name: "Keyword",
   122  		ExtraData: []*internal.IndexerData{
   123  			{ID: 1000, Title: "hi hello world"},
   124  			{ID: 1001, Content: "hi hello world"},
   125  			{ID: 1002, Comments: []string{"hi", "hello world"}},
   126  		},
   127  		SearchOptions: &internal.SearchOptions{
   128  			Keyword: "hello",
   129  		},
   130  		ExpectedIDs:   []int64{1002, 1001, 1000},
   131  		ExpectedTotal: 3,
   132  	},
   133  	{
   134  		Name: "RepoIDs",
   135  		ExtraData: []*internal.IndexerData{
   136  			{ID: 1001, Title: "hello world", RepoID: 1, IsPublic: false},
   137  			{ID: 1002, Title: "hello world", RepoID: 1, IsPublic: false},
   138  			{ID: 1003, Title: "hello world", RepoID: 2, IsPublic: true},
   139  			{ID: 1004, Title: "hello world", RepoID: 2, IsPublic: true},
   140  			{ID: 1005, Title: "hello world", RepoID: 3, IsPublic: true},
   141  			{ID: 1006, Title: "hello world", RepoID: 4, IsPublic: false},
   142  			{ID: 1007, Title: "hello world", RepoID: 5, IsPublic: false},
   143  		},
   144  		SearchOptions: &internal.SearchOptions{
   145  			Keyword: "hello",
   146  			RepoIDs: []int64{1, 4},
   147  		},
   148  		ExpectedIDs:   []int64{1006, 1002, 1001},
   149  		ExpectedTotal: 3,
   150  	},
   151  	{
   152  		Name: "RepoIDs and AllPublic",
   153  		ExtraData: []*internal.IndexerData{
   154  			{ID: 1001, Title: "hello world", RepoID: 1, IsPublic: false},
   155  			{ID: 1002, Title: "hello world", RepoID: 1, IsPublic: false},
   156  			{ID: 1003, Title: "hello world", RepoID: 2, IsPublic: true},
   157  			{ID: 1004, Title: "hello world", RepoID: 2, IsPublic: true},
   158  			{ID: 1005, Title: "hello world", RepoID: 3, IsPublic: true},
   159  			{ID: 1006, Title: "hello world", RepoID: 4, IsPublic: false},
   160  			{ID: 1007, Title: "hello world", RepoID: 5, IsPublic: false},
   161  		},
   162  		SearchOptions: &internal.SearchOptions{
   163  			Keyword:   "hello",
   164  			RepoIDs:   []int64{1, 4},
   165  			AllPublic: true,
   166  		},
   167  		ExpectedIDs:   []int64{1006, 1005, 1004, 1003, 1002, 1001},
   168  		ExpectedTotal: 6,
   169  	},
   170  	{
   171  		Name: "issue only",
   172  		SearchOptions: &internal.SearchOptions{
   173  			Paginator: &db.ListOptions{
   174  				PageSize: 5,
   175  			},
   176  			IsPull: optional.Some(false),
   177  		},
   178  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   179  			assert.Equal(t, 5, len(result.Hits))
   180  			for _, v := range result.Hits {
   181  				assert.False(t, data[v.ID].IsPull)
   182  			}
   183  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return !v.IsPull }), result.Total)
   184  		},
   185  	},
   186  	{
   187  		Name: "pull only",
   188  		SearchOptions: &internal.SearchOptions{
   189  			Paginator: &db.ListOptions{
   190  				PageSize: 5,
   191  			},
   192  			IsPull: optional.Some(true),
   193  		},
   194  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   195  			assert.Equal(t, 5, len(result.Hits))
   196  			for _, v := range result.Hits {
   197  				assert.True(t, data[v.ID].IsPull)
   198  			}
   199  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return v.IsPull }), result.Total)
   200  		},
   201  	},
   202  	{
   203  		Name: "opened only",
   204  		SearchOptions: &internal.SearchOptions{
   205  			Paginator: &db.ListOptions{
   206  				PageSize: 5,
   207  			},
   208  			IsClosed: optional.Some(false),
   209  		},
   210  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   211  			assert.Equal(t, 5, len(result.Hits))
   212  			for _, v := range result.Hits {
   213  				assert.False(t, data[v.ID].IsClosed)
   214  			}
   215  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return !v.IsClosed }), result.Total)
   216  		},
   217  	},
   218  	{
   219  		Name: "closed only",
   220  		SearchOptions: &internal.SearchOptions{
   221  			Paginator: &db.ListOptions{
   222  				PageSize: 5,
   223  			},
   224  			IsClosed: optional.Some(true),
   225  		},
   226  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   227  			assert.Equal(t, 5, len(result.Hits))
   228  			for _, v := range result.Hits {
   229  				assert.True(t, data[v.ID].IsClosed)
   230  			}
   231  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return v.IsClosed }), result.Total)
   232  		},
   233  	},
   234  	{
   235  		Name: "labels",
   236  		ExtraData: []*internal.IndexerData{
   237  			{ID: 1000, Title: "hello a", LabelIDs: []int64{2000, 2001, 2002}},
   238  			{ID: 1001, Title: "hello b", LabelIDs: []int64{2000, 2001}},
   239  			{ID: 1002, Title: "hello c", LabelIDs: []int64{2000, 2001, 2003}},
   240  			{ID: 1003, Title: "hello d", LabelIDs: []int64{2000}},
   241  			{ID: 1004, Title: "hello e", LabelIDs: []int64{}},
   242  		},
   243  		SearchOptions: &internal.SearchOptions{
   244  			Keyword:          "hello",
   245  			IncludedLabelIDs: []int64{2000, 2001},
   246  			ExcludedLabelIDs: []int64{2003},
   247  		},
   248  		ExpectedIDs:   []int64{1001, 1000},
   249  		ExpectedTotal: 2,
   250  	},
   251  	{
   252  		Name: "include any labels",
   253  		ExtraData: []*internal.IndexerData{
   254  			{ID: 1000, Title: "hello a", LabelIDs: []int64{2000, 2001, 2002}},
   255  			{ID: 1001, Title: "hello b", LabelIDs: []int64{2001}},
   256  			{ID: 1002, Title: "hello c", LabelIDs: []int64{2000, 2001, 2003}},
   257  			{ID: 1003, Title: "hello d", LabelIDs: []int64{2002}},
   258  			{ID: 1004, Title: "hello e", LabelIDs: []int64{}},
   259  		},
   260  		SearchOptions: &internal.SearchOptions{
   261  			Keyword:             "hello",
   262  			IncludedAnyLabelIDs: []int64{2001, 2002},
   263  			ExcludedLabelIDs:    []int64{2003},
   264  		},
   265  		ExpectedIDs:   []int64{1003, 1001, 1000},
   266  		ExpectedTotal: 3,
   267  	},
   268  	{
   269  		Name: "MilestoneIDs",
   270  		SearchOptions: &internal.SearchOptions{
   271  			Paginator: &db.ListOptions{
   272  				PageSize: 5,
   273  			},
   274  			MilestoneIDs: []int64{1, 2, 6},
   275  		},
   276  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   277  			assert.Equal(t, 5, len(result.Hits))
   278  			for _, v := range result.Hits {
   279  				assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID)
   280  			}
   281  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   282  				return v.MilestoneID == 1 || v.MilestoneID == 2 || v.MilestoneID == 6
   283  			}), result.Total)
   284  		},
   285  	},
   286  	{
   287  		Name: "no MilestoneIDs",
   288  		SearchOptions: &internal.SearchOptions{
   289  			Paginator: &db.ListOptions{
   290  				PageSize: 5,
   291  			},
   292  			MilestoneIDs: []int64{0},
   293  		},
   294  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   295  			assert.Equal(t, 5, len(result.Hits))
   296  			for _, v := range result.Hits {
   297  				assert.Equal(t, int64(0), data[v.ID].MilestoneID)
   298  			}
   299  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   300  				return v.MilestoneID == 0
   301  			}), result.Total)
   302  		},
   303  	},
   304  	{
   305  		Name: "ProjectID",
   306  		SearchOptions: &internal.SearchOptions{
   307  			Paginator: &db.ListOptions{
   308  				PageSize: 5,
   309  			},
   310  			ProjectID: optional.Some(int64(1)),
   311  		},
   312  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   313  			assert.Equal(t, 5, len(result.Hits))
   314  			for _, v := range result.Hits {
   315  				assert.Equal(t, int64(1), data[v.ID].ProjectID)
   316  			}
   317  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   318  				return v.ProjectID == 1
   319  			}), result.Total)
   320  		},
   321  	},
   322  	{
   323  		Name: "no ProjectID",
   324  		SearchOptions: &internal.SearchOptions{
   325  			Paginator: &db.ListOptions{
   326  				PageSize: 5,
   327  			},
   328  			ProjectID: optional.Some(int64(0)),
   329  		},
   330  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   331  			assert.Equal(t, 5, len(result.Hits))
   332  			for _, v := range result.Hits {
   333  				assert.Equal(t, int64(0), data[v.ID].ProjectID)
   334  			}
   335  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   336  				return v.ProjectID == 0
   337  			}), result.Total)
   338  		},
   339  	},
   340  	{
   341  		Name: "ProjectBoardID",
   342  		SearchOptions: &internal.SearchOptions{
   343  			Paginator: &db.ListOptions{
   344  				PageSize: 5,
   345  			},
   346  			ProjectBoardID: optional.Some(int64(1)),
   347  		},
   348  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   349  			assert.Equal(t, 5, len(result.Hits))
   350  			for _, v := range result.Hits {
   351  				assert.Equal(t, int64(1), data[v.ID].ProjectBoardID)
   352  			}
   353  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   354  				return v.ProjectBoardID == 1
   355  			}), result.Total)
   356  		},
   357  	},
   358  	{
   359  		Name: "no ProjectBoardID",
   360  		SearchOptions: &internal.SearchOptions{
   361  			Paginator: &db.ListOptions{
   362  				PageSize: 5,
   363  			},
   364  			ProjectBoardID: optional.Some(int64(0)),
   365  		},
   366  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   367  			assert.Equal(t, 5, len(result.Hits))
   368  			for _, v := range result.Hits {
   369  				assert.Equal(t, int64(0), data[v.ID].ProjectBoardID)
   370  			}
   371  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   372  				return v.ProjectBoardID == 0
   373  			}), result.Total)
   374  		},
   375  	},
   376  	{
   377  		Name: "PosterID",
   378  		SearchOptions: &internal.SearchOptions{
   379  			Paginator: &db.ListOptions{
   380  				PageSize: 5,
   381  			},
   382  			PosterID: optional.Some(int64(1)),
   383  		},
   384  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   385  			assert.Equal(t, 5, len(result.Hits))
   386  			for _, v := range result.Hits {
   387  				assert.Equal(t, int64(1), data[v.ID].PosterID)
   388  			}
   389  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   390  				return v.PosterID == 1
   391  			}), result.Total)
   392  		},
   393  	},
   394  	{
   395  		Name: "AssigneeID",
   396  		SearchOptions: &internal.SearchOptions{
   397  			Paginator: &db.ListOptions{
   398  				PageSize: 5,
   399  			},
   400  			AssigneeID: optional.Some(int64(1)),
   401  		},
   402  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   403  			assert.Equal(t, 5, len(result.Hits))
   404  			for _, v := range result.Hits {
   405  				assert.Equal(t, int64(1), data[v.ID].AssigneeID)
   406  			}
   407  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   408  				return v.AssigneeID == 1
   409  			}), result.Total)
   410  		},
   411  	},
   412  	{
   413  		Name: "no AssigneeID",
   414  		SearchOptions: &internal.SearchOptions{
   415  			Paginator: &db.ListOptions{
   416  				PageSize: 5,
   417  			},
   418  			AssigneeID: optional.Some(int64(0)),
   419  		},
   420  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   421  			assert.Equal(t, 5, len(result.Hits))
   422  			for _, v := range result.Hits {
   423  				assert.Equal(t, int64(0), data[v.ID].AssigneeID)
   424  			}
   425  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   426  				return v.AssigneeID == 0
   427  			}), result.Total)
   428  		},
   429  	},
   430  	{
   431  		Name: "MentionID",
   432  		SearchOptions: &internal.SearchOptions{
   433  			Paginator: &db.ListOptions{
   434  				PageSize: 5,
   435  			},
   436  			MentionID: optional.Some(int64(1)),
   437  		},
   438  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   439  			assert.Equal(t, 5, len(result.Hits))
   440  			for _, v := range result.Hits {
   441  				assert.Contains(t, data[v.ID].MentionIDs, int64(1))
   442  			}
   443  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   444  				return slices.Contains(v.MentionIDs, 1)
   445  			}), result.Total)
   446  		},
   447  	},
   448  	{
   449  		Name: "ReviewedID",
   450  		SearchOptions: &internal.SearchOptions{
   451  			Paginator: &db.ListOptions{
   452  				PageSize: 5,
   453  			},
   454  			ReviewedID: optional.Some(int64(1)),
   455  		},
   456  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   457  			assert.Equal(t, 5, len(result.Hits))
   458  			for _, v := range result.Hits {
   459  				assert.Contains(t, data[v.ID].ReviewedIDs, int64(1))
   460  			}
   461  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   462  				return slices.Contains(v.ReviewedIDs, 1)
   463  			}), result.Total)
   464  		},
   465  	},
   466  	{
   467  		Name: "ReviewRequestedID",
   468  		SearchOptions: &internal.SearchOptions{
   469  			Paginator: &db.ListOptions{
   470  				PageSize: 5,
   471  			},
   472  			ReviewRequestedID: optional.Some(int64(1)),
   473  		},
   474  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   475  			assert.Equal(t, 5, len(result.Hits))
   476  			for _, v := range result.Hits {
   477  				assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1))
   478  			}
   479  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   480  				return slices.Contains(v.ReviewRequestedIDs, 1)
   481  			}), result.Total)
   482  		},
   483  	},
   484  	{
   485  		Name: "SubscriberID",
   486  		SearchOptions: &internal.SearchOptions{
   487  			Paginator: &db.ListOptions{
   488  				PageSize: 5,
   489  			},
   490  			SubscriberID: optional.Some(int64(1)),
   491  		},
   492  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   493  			assert.Equal(t, 5, len(result.Hits))
   494  			for _, v := range result.Hits {
   495  				assert.Contains(t, data[v.ID].SubscriberIDs, int64(1))
   496  			}
   497  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   498  				return slices.Contains(v.SubscriberIDs, 1)
   499  			}), result.Total)
   500  		},
   501  	},
   502  	{
   503  		Name: "updated",
   504  		SearchOptions: &internal.SearchOptions{
   505  			Paginator: &db.ListOptions{
   506  				PageSize: 5,
   507  			},
   508  			UpdatedAfterUnix:  optional.Some(int64(20)),
   509  			UpdatedBeforeUnix: optional.Some(int64(30)),
   510  		},
   511  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   512  			assert.Equal(t, 5, len(result.Hits))
   513  			for _, v := range result.Hits {
   514  				assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20))
   515  				assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30))
   516  			}
   517  			assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
   518  				return data[v.ID].UpdatedUnix >= 20 && data[v.ID].UpdatedUnix <= 30
   519  			}), result.Total)
   520  		},
   521  	},
   522  	{
   523  		Name: "SortByCreatedDesc",
   524  		SearchOptions: &internal.SearchOptions{
   525  			Paginator: &db.ListOptionsAll,
   526  			SortBy:    internal.SortByCreatedDesc,
   527  		},
   528  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   529  			assert.Equal(t, len(data), len(result.Hits))
   530  			assert.Equal(t, len(data), int(result.Total))
   531  			for i, v := range result.Hits {
   532  				if i < len(result.Hits)-1 {
   533  					assert.GreaterOrEqual(t, data[v.ID].CreatedUnix, data[result.Hits[i+1].ID].CreatedUnix)
   534  				}
   535  			}
   536  		},
   537  	},
   538  	{
   539  		Name: "SortByUpdatedDesc",
   540  		SearchOptions: &internal.SearchOptions{
   541  			Paginator: &db.ListOptionsAll,
   542  			SortBy:    internal.SortByUpdatedDesc,
   543  		},
   544  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   545  			assert.Equal(t, len(data), len(result.Hits))
   546  			assert.Equal(t, len(data), int(result.Total))
   547  			for i, v := range result.Hits {
   548  				if i < len(result.Hits)-1 {
   549  					assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, data[result.Hits[i+1].ID].UpdatedUnix)
   550  				}
   551  			}
   552  		},
   553  	},
   554  	{
   555  		Name: "SortByCommentsDesc",
   556  		SearchOptions: &internal.SearchOptions{
   557  			Paginator: &db.ListOptionsAll,
   558  			SortBy:    internal.SortByCommentsDesc,
   559  		},
   560  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   561  			assert.Equal(t, len(data), len(result.Hits))
   562  			assert.Equal(t, len(data), int(result.Total))
   563  			for i, v := range result.Hits {
   564  				if i < len(result.Hits)-1 {
   565  					assert.GreaterOrEqual(t, data[v.ID].CommentCount, data[result.Hits[i+1].ID].CommentCount)
   566  				}
   567  			}
   568  		},
   569  	},
   570  	{
   571  		Name: "SortByDeadlineDesc",
   572  		SearchOptions: &internal.SearchOptions{
   573  			Paginator: &db.ListOptionsAll,
   574  			SortBy:    internal.SortByDeadlineDesc,
   575  		},
   576  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   577  			assert.Equal(t, len(data), len(result.Hits))
   578  			assert.Equal(t, len(data), int(result.Total))
   579  			for i, v := range result.Hits {
   580  				if i < len(result.Hits)-1 {
   581  					assert.GreaterOrEqual(t, data[v.ID].DeadlineUnix, data[result.Hits[i+1].ID].DeadlineUnix)
   582  				}
   583  			}
   584  		},
   585  	},
   586  	{
   587  		Name: "SortByCreatedAsc",
   588  		SearchOptions: &internal.SearchOptions{
   589  			Paginator: &db.ListOptionsAll,
   590  			SortBy:    internal.SortByCreatedAsc,
   591  		},
   592  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   593  			assert.Equal(t, len(data), len(result.Hits))
   594  			assert.Equal(t, len(data), int(result.Total))
   595  			for i, v := range result.Hits {
   596  				if i < len(result.Hits)-1 {
   597  					assert.LessOrEqual(t, data[v.ID].CreatedUnix, data[result.Hits[i+1].ID].CreatedUnix)
   598  				}
   599  			}
   600  		},
   601  	},
   602  	{
   603  		Name: "SortByUpdatedAsc",
   604  		SearchOptions: &internal.SearchOptions{
   605  			Paginator: &db.ListOptionsAll,
   606  			SortBy:    internal.SortByUpdatedAsc,
   607  		},
   608  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   609  			assert.Equal(t, len(data), len(result.Hits))
   610  			assert.Equal(t, len(data), int(result.Total))
   611  			for i, v := range result.Hits {
   612  				if i < len(result.Hits)-1 {
   613  					assert.LessOrEqual(t, data[v.ID].UpdatedUnix, data[result.Hits[i+1].ID].UpdatedUnix)
   614  				}
   615  			}
   616  		},
   617  	},
   618  	{
   619  		Name: "SortByCommentsAsc",
   620  		SearchOptions: &internal.SearchOptions{
   621  			Paginator: &db.ListOptionsAll,
   622  			SortBy:    internal.SortByCommentsAsc,
   623  		},
   624  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   625  			assert.Equal(t, len(data), len(result.Hits))
   626  			assert.Equal(t, len(data), int(result.Total))
   627  			for i, v := range result.Hits {
   628  				if i < len(result.Hits)-1 {
   629  					assert.LessOrEqual(t, data[v.ID].CommentCount, data[result.Hits[i+1].ID].CommentCount)
   630  				}
   631  			}
   632  		},
   633  	},
   634  	{
   635  		Name: "SortByDeadlineAsc",
   636  		SearchOptions: &internal.SearchOptions{
   637  			Paginator: &db.ListOptionsAll,
   638  			SortBy:    internal.SortByDeadlineAsc,
   639  		},
   640  		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
   641  			assert.Equal(t, len(data), len(result.Hits))
   642  			assert.Equal(t, len(data), int(result.Total))
   643  			for i, v := range result.Hits {
   644  				if i < len(result.Hits)-1 {
   645  					assert.LessOrEqual(t, data[v.ID].DeadlineUnix, data[result.Hits[i+1].ID].DeadlineUnix)
   646  				}
   647  			}
   648  		},
   649  	},
   650  }
   651  
   652  type testIndexerCase struct {
   653  	Name      string
   654  	ExtraData []*internal.IndexerData
   655  
   656  	SearchOptions *internal.SearchOptions
   657  
   658  	Expected      func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) // if nil, use ExpectedIDs, ExpectedTotal
   659  	ExpectedIDs   []int64
   660  	ExpectedTotal int64
   661  }
   662  
   663  func generateDefaultIndexerData() []*internal.IndexerData {
   664  	var id int64
   665  	var data []*internal.IndexerData
   666  	for repoID := int64(1); repoID <= 10; repoID++ {
   667  		for issueIndex := int64(1); issueIndex <= 20; issueIndex++ {
   668  			id++
   669  
   670  			comments := make([]string, id%4)
   671  			for i := range comments {
   672  				comments[i] = fmt.Sprintf("comment%d", i)
   673  			}
   674  
   675  			labelIDs := make([]int64, id%5)
   676  			for i := range labelIDs {
   677  				labelIDs[i] = int64(i) + 1 // LabelID should not be 0
   678  			}
   679  			mentionIDs := make([]int64, id%6)
   680  			for i := range mentionIDs {
   681  				mentionIDs[i] = int64(i) + 1 // MentionID should not be 0
   682  			}
   683  			reviewedIDs := make([]int64, id%7)
   684  			for i := range reviewedIDs {
   685  				reviewedIDs[i] = int64(i) + 1 // ReviewID should not be 0
   686  			}
   687  			reviewRequestedIDs := make([]int64, id%8)
   688  			for i := range reviewRequestedIDs {
   689  				reviewRequestedIDs[i] = int64(i) + 1 // ReviewRequestedID should not be 0
   690  			}
   691  			subscriberIDs := make([]int64, id%9)
   692  			for i := range subscriberIDs {
   693  				subscriberIDs[i] = int64(i) + 1 // SubscriberID should not be 0
   694  			}
   695  
   696  			data = append(data, &internal.IndexerData{
   697  				ID:                 id,
   698  				RepoID:             repoID,
   699  				IsPublic:           repoID%2 == 0,
   700  				Title:              fmt.Sprintf("issue%d of repo%d", issueIndex, repoID),
   701  				Content:            fmt.Sprintf("content%d", issueIndex),
   702  				Comments:           comments,
   703  				IsPull:             issueIndex%2 == 0,
   704  				IsClosed:           issueIndex%3 == 0,
   705  				LabelIDs:           labelIDs,
   706  				NoLabel:            len(labelIDs) == 0,
   707  				MilestoneID:        issueIndex % 4,
   708  				ProjectID:          issueIndex % 5,
   709  				ProjectBoardID:     issueIndex % 6,
   710  				PosterID:           id%10 + 1, // PosterID should not be 0
   711  				AssigneeID:         issueIndex % 10,
   712  				MentionIDs:         mentionIDs,
   713  				ReviewedIDs:        reviewedIDs,
   714  				ReviewRequestedIDs: reviewRequestedIDs,
   715  				SubscriberIDs:      subscriberIDs,
   716  				UpdatedUnix:        timeutil.TimeStamp(id + issueIndex),
   717  				CreatedUnix:        timeutil.TimeStamp(id),
   718  				DeadlineUnix:       timeutil.TimeStamp(id + issueIndex + repoID),
   719  				CommentCount:       int64(len(comments)),
   720  			})
   721  		}
   722  	}
   723  
   724  	return data
   725  }
   726  
   727  func countIndexerData(data map[int64]*internal.IndexerData, f func(v *internal.IndexerData) bool) int64 {
   728  	var count int64
   729  	for _, v := range data {
   730  		if f(v) {
   731  			count++
   732  		}
   733  	}
   734  	return count
   735  }
   736  
   737  // waitData waits for the indexer to index all data.
   738  // Some engines like Elasticsearch index data asynchronously, so we need to wait for a while.
   739  func waitData(indexer internal.Indexer, total int64) error {
   740  	var actual int64
   741  	for i := 0; i < 100; i++ {
   742  		result, err := indexer.Search(context.Background(), &internal.SearchOptions{
   743  			Paginator: &db.ListOptions{
   744  				PageSize: 0,
   745  			},
   746  		})
   747  		if err != nil {
   748  			return err
   749  		}
   750  		actual = result.Total
   751  		if actual == total {
   752  			return nil
   753  		}
   754  		time.Sleep(100 * time.Millisecond)
   755  	}
   756  	return fmt.Errorf("waitData: expected %d, actual %d", total, actual)
   757  }