mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
Integration test processes now get their own work directory, config file and random ports, so several runs can execute in parallel on one machine. The slowest tests also lose fixed sleeps and needlessly expensive setup, with their assertions kept. - Batch PostgreSQL sequence resets, the main per-test fixture cost there - Cheap hashing params in hash and OAuth2 fixture tests, full-cost vectors stay - Single fetch for negative Actions runner assertions instead of 1s polls - Queue flushes and backdated timestamps instead of sleeps - In-memory SQLite test journal, tmpfs for PostgreSQL in CI - E2E instance writes SSH keys into its work directory instead of `~/.ssh/authorized_keys`
757 lines
23 KiB
Go
757 lines
23 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// This package contains tests for issues indexer modules.
|
|
// All the code in this package is only used for testing.
|
|
// Do not put any production code in this package to avoid it being included in the final binary.
|
|
|
|
package tests
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dev/models/db"
|
|
"gitea.dev/modules/indexer/issues/internal"
|
|
"gitea.dev/modules/optional"
|
|
"gitea.dev/modules/timeutil"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestIndexer(t *testing.T, indexer internal.Indexer) {
|
|
_, err := indexer.Init(t.Context())
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, indexer.Ping(t.Context()))
|
|
|
|
var (
|
|
ids []int64
|
|
data = map[int64]*internal.IndexerData{}
|
|
)
|
|
{
|
|
d := generateDefaultIndexerData()
|
|
for _, v := range d {
|
|
ids = append(ids, v.ID)
|
|
data[v.ID] = v
|
|
}
|
|
require.NoError(t, indexer.Index(t.Context(), d...))
|
|
waitData(t, indexer, int64(len(data)))
|
|
}
|
|
|
|
defer func() {
|
|
require.NoError(t, indexer.Delete(t.Context(), ids...))
|
|
}()
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
if len(c.ExtraData) > 0 {
|
|
require.NoError(t, indexer.Index(t.Context(), c.ExtraData...))
|
|
for _, v := range c.ExtraData {
|
|
data[v.ID] = v
|
|
}
|
|
waitData(t, indexer, int64(len(data)))
|
|
defer func() {
|
|
for _, v := range c.ExtraData {
|
|
require.NoError(t, indexer.Delete(t.Context(), v.ID))
|
|
delete(data, v.ID)
|
|
}
|
|
waitData(t, indexer, int64(len(data)))
|
|
}()
|
|
}
|
|
|
|
result, err := indexer.Search(t.Context(), c.SearchOptions)
|
|
require.NoError(t, err)
|
|
|
|
if c.Expected != nil {
|
|
c.Expected(t, data, result)
|
|
} else {
|
|
ids := make([]int64, 0, len(result.Hits))
|
|
for _, hit := range result.Hits {
|
|
ids = append(ids, hit.ID)
|
|
}
|
|
assert.Equal(t, c.ExpectedIDs, ids)
|
|
assert.Equal(t, c.ExpectedTotal, result.Total)
|
|
}
|
|
|
|
// test counting
|
|
c.SearchOptions.Paginator = &db.ListOptions{PageSize: 0}
|
|
countResult, err := indexer.Search(t.Context(), c.SearchOptions)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, countResult.Hits)
|
|
assert.Equal(t, result.Total, countResult.Total)
|
|
})
|
|
}
|
|
}
|
|
|
|
var cases = []*testIndexerCase{
|
|
{
|
|
Name: "default",
|
|
SearchOptions: &internal.SearchOptions{},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
},
|
|
},
|
|
{
|
|
Name: "empty",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Keyword: "f1dfac73-fda6-4a6b-b8a4-2408fcb8ef69",
|
|
},
|
|
ExpectedIDs: []int64{},
|
|
ExpectedTotal: 0,
|
|
},
|
|
{
|
|
Name: "with limit",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
},
|
|
},
|
|
{
|
|
// Exercises the single-doc Index/Delete fast path in backends that have one (e.g. Elasticsearch).
|
|
Name: "single-doc index",
|
|
ExtraData: []*internal.IndexerData{
|
|
{ID: 999, Title: "solo-issue-marker"},
|
|
},
|
|
SearchOptions: &internal.SearchOptions{Keyword: "solo-issue-marker"},
|
|
ExpectedIDs: []int64{999},
|
|
ExpectedTotal: 1,
|
|
},
|
|
{
|
|
Name: "Keyword",
|
|
ExtraData: []*internal.IndexerData{
|
|
{ID: 1000, Title: "hi hello world"},
|
|
{ID: 1001, Content: "hi hello world"},
|
|
{ID: 1002, Comments: []string{"hi", "hello world"}},
|
|
},
|
|
SearchOptions: &internal.SearchOptions{
|
|
Keyword: "hello",
|
|
},
|
|
ExpectedIDs: []int64{1002, 1001, 1000},
|
|
ExpectedTotal: 3,
|
|
},
|
|
{
|
|
Name: "RepoIDs",
|
|
ExtraData: []*internal.IndexerData{
|
|
{ID: 1001, Title: "hello world", RepoID: 1, IsPublic: false},
|
|
{ID: 1002, Title: "hello world", RepoID: 1, IsPublic: false},
|
|
{ID: 1003, Title: "hello world", RepoID: 2, IsPublic: true},
|
|
{ID: 1004, Title: "hello world", RepoID: 2, IsPublic: true},
|
|
{ID: 1005, Title: "hello world", RepoID: 3, IsPublic: true},
|
|
{ID: 1006, Title: "hello world", RepoID: 4, IsPublic: false},
|
|
{ID: 1007, Title: "hello world", RepoID: 5, IsPublic: false},
|
|
},
|
|
SearchOptions: &internal.SearchOptions{
|
|
Keyword: "hello",
|
|
RepoIDs: []int64{1, 4},
|
|
},
|
|
ExpectedIDs: []int64{1006, 1002, 1001},
|
|
ExpectedTotal: 3,
|
|
},
|
|
{
|
|
Name: "RepoIDs and AllPublic",
|
|
ExtraData: []*internal.IndexerData{
|
|
{ID: 1001, Title: "hello world", RepoID: 1, IsPublic: false},
|
|
{ID: 1002, Title: "hello world", RepoID: 1, IsPublic: false},
|
|
{ID: 1003, Title: "hello world", RepoID: 2, IsPublic: true},
|
|
{ID: 1004, Title: "hello world", RepoID: 2, IsPublic: true},
|
|
{ID: 1005, Title: "hello world", RepoID: 3, IsPublic: true},
|
|
{ID: 1006, Title: "hello world", RepoID: 4, IsPublic: false},
|
|
{ID: 1007, Title: "hello world", RepoID: 5, IsPublic: false},
|
|
},
|
|
SearchOptions: &internal.SearchOptions{
|
|
Keyword: "hello",
|
|
RepoIDs: []int64{1, 4},
|
|
AllPublic: true,
|
|
},
|
|
ExpectedIDs: []int64{1006, 1005, 1004, 1003, 1002, 1001},
|
|
ExpectedTotal: 6,
|
|
},
|
|
{
|
|
Name: "issue only",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
IsPull: optional.Some(false),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.False(t, data[v.ID].IsPull)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return !v.IsPull }), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "pull only",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
IsPull: optional.Some(true),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.True(t, data[v.ID].IsPull)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return v.IsPull }), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "opened only",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
IsClosed: optional.Some(false),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.False(t, data[v.ID].IsClosed)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return !v.IsClosed }), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "closed only",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
IsClosed: optional.Some(true),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.True(t, data[v.ID].IsClosed)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { return v.IsClosed }), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "labels",
|
|
ExtraData: []*internal.IndexerData{
|
|
{ID: 1000, Title: "hello a", LabelIDs: []int64{2000, 2001, 2002}},
|
|
{ID: 1001, Title: "hello b", LabelIDs: []int64{2000, 2001}},
|
|
{ID: 1002, Title: "hello c", LabelIDs: []int64{2000, 2001, 2003}},
|
|
{ID: 1003, Title: "hello d", LabelIDs: []int64{2000}},
|
|
{ID: 1004, Title: "hello e", LabelIDs: []int64{}},
|
|
},
|
|
SearchOptions: &internal.SearchOptions{
|
|
Keyword: "hello",
|
|
IncludedLabelIDs: []int64{2000, 2001},
|
|
ExcludedLabelIDs: []int64{2003},
|
|
},
|
|
ExpectedIDs: []int64{1001, 1000},
|
|
ExpectedTotal: 2,
|
|
},
|
|
{
|
|
Name: "include any labels",
|
|
ExtraData: []*internal.IndexerData{
|
|
{ID: 1000, Title: "hello a", LabelIDs: []int64{2000, 2001, 2002}},
|
|
{ID: 1001, Title: "hello b", LabelIDs: []int64{2001}},
|
|
{ID: 1002, Title: "hello c", LabelIDs: []int64{2000, 2001, 2003}},
|
|
{ID: 1003, Title: "hello d", LabelIDs: []int64{2002}},
|
|
{ID: 1004, Title: "hello e", LabelIDs: []int64{}},
|
|
},
|
|
SearchOptions: &internal.SearchOptions{
|
|
Keyword: "hello",
|
|
IncludedAnyLabelIDs: []int64{2001, 2002},
|
|
ExcludedLabelIDs: []int64{2003},
|
|
},
|
|
ExpectedIDs: []int64{1003, 1001, 1000},
|
|
ExpectedTotal: 3,
|
|
},
|
|
{
|
|
Name: "MilestoneIDs",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
MilestoneIDs: []int64{1, 2, 6},
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return v.MilestoneID == 1 || v.MilestoneID == 2 || v.MilestoneID == 6
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "no MilestoneIDs",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
MilestoneIDs: []int64{0},
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Equal(t, int64(0), data[v.ID].MilestoneID)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return v.MilestoneID == 0
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "ProjectIDs",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
ProjectIDs: []int64{1},
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Contains(t, data[v.ID].ProjectIDs, int64(1))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return slices.Contains(v.ProjectIDs, int64(1))
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "no ProjectIDs (empty array)",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 50,
|
|
},
|
|
NoProjectOnly: true,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
// Verify only issues with no projects are returned
|
|
for _, v := range result.Hits {
|
|
assert.Empty(t, data[v.ID].ProjectIDs, "Issue %d should have no projects", v.ID)
|
|
}
|
|
// Verify we got ALL issues with no projects
|
|
expectedCount := countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return len(v.ProjectIDs) == 0
|
|
})
|
|
assert.Equal(t, expectedCount, result.Total, "Should return all %d issues with no project", expectedCount)
|
|
},
|
|
},
|
|
{
|
|
Name: "PosterID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
PosterID: "1",
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Equal(t, int64(1), data[v.ID].PosterID)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return v.PosterID == 1
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "AssigneeID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
AssigneeID: "1",
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.True(t, slices.Contains(data[v.ID].AssigneeIDs, int64(1)))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return slices.Contains(v.AssigneeIDs, int64(1))
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "no AssigneeID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
AssigneeID: "(none)",
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.True(t, data[v.ID].NoAssignee)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return v.NoAssignee
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "MentionID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
MentionID: optional.Some(int64(1)),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Contains(t, data[v.ID].MentionIDs, int64(1))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return slices.Contains(v.MentionIDs, 1)
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "ReviewedID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
ReviewedID: optional.Some(int64(1)),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Contains(t, data[v.ID].ReviewedIDs, int64(1))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return slices.Contains(v.ReviewedIDs, 1)
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "ReviewRequestedID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
ReviewRequestedID: optional.Some(int64(1)),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return slices.Contains(v.ReviewRequestedIDs, 1)
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "SubscriberID",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
SubscriberID: optional.Some(int64(1)),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.Contains(t, data[v.ID].SubscriberIDs, int64(1))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return slices.Contains(v.SubscriberIDs, 1)
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "updated",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptions{
|
|
PageSize: 5,
|
|
},
|
|
UpdatedAfterUnix: optional.Some(int64(20)),
|
|
UpdatedBeforeUnix: optional.Some(int64(30)),
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 5)
|
|
for _, v := range result.Hits {
|
|
assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20))
|
|
assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30))
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return data[v.ID].UpdatedUnix >= 20 && data[v.ID].UpdatedUnix <= 30
|
|
}), result.Total)
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByCreatedDesc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByCreatedDesc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.GreaterOrEqual(t, data[v.ID].CreatedUnix, data[result.Hits[i+1].ID].CreatedUnix)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByUpdatedDesc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByUpdatedDesc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, data[result.Hits[i+1].ID].UpdatedUnix)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByCommentsDesc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByCommentsDesc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.GreaterOrEqual(t, data[v.ID].CommentCount, data[result.Hits[i+1].ID].CommentCount)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByDeadlineDesc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByDeadlineDesc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.GreaterOrEqual(t, data[v.ID].DeadlineUnix, data[result.Hits[i+1].ID].DeadlineUnix)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByCreatedAsc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByCreatedAsc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.LessOrEqual(t, data[v.ID].CreatedUnix, data[result.Hits[i+1].ID].CreatedUnix)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByUpdatedAsc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByUpdatedAsc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.LessOrEqual(t, data[v.ID].UpdatedUnix, data[result.Hits[i+1].ID].UpdatedUnix)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByCommentsAsc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByCommentsAsc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.LessOrEqual(t, data[v.ID].CommentCount, data[result.Hits[i+1].ID].CommentCount)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SortByDeadlineAsc",
|
|
SearchOptions: &internal.SearchOptions{
|
|
Paginator: &db.ListOptionsAll,
|
|
SortBy: internal.SortByDeadlineAsc,
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, len(data))
|
|
assert.Equal(t, len(data), int(result.Total))
|
|
for i, v := range result.Hits {
|
|
if i < len(result.Hits)-1 {
|
|
assert.LessOrEqual(t, data[v.ID].DeadlineUnix, data[result.Hits[i+1].ID].DeadlineUnix)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "SearchAnyAssignee",
|
|
SearchOptions: &internal.SearchOptions{
|
|
AssigneeID: "(any)",
|
|
},
|
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
|
assert.Len(t, result.Hits, 180)
|
|
for _, v := range result.Hits {
|
|
assert.False(t, data[v.ID].NoAssignee)
|
|
}
|
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
|
return !v.NoAssignee
|
|
}), result.Total)
|
|
},
|
|
},
|
|
}
|
|
|
|
type testIndexerCase struct {
|
|
Name string
|
|
ExtraData []*internal.IndexerData
|
|
|
|
SearchOptions *internal.SearchOptions
|
|
|
|
Expected func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) // if nil, use ExpectedIDs, ExpectedTotal
|
|
ExpectedIDs []int64
|
|
ExpectedTotal int64
|
|
}
|
|
|
|
func generateDefaultIndexerData() []*internal.IndexerData {
|
|
var id int64
|
|
var data []*internal.IndexerData
|
|
for repoID := int64(1); repoID <= 10; repoID++ {
|
|
for issueIndex := int64(1); issueIndex <= 20; issueIndex++ {
|
|
id++
|
|
|
|
comments := make([]string, id%4)
|
|
for i := range comments {
|
|
comments[i] = fmt.Sprintf("comment%d", i)
|
|
}
|
|
|
|
labelIDs := make([]int64, id%5)
|
|
for i := range labelIDs {
|
|
labelIDs[i] = int64(i) + 1 // LabelID should not be 0
|
|
}
|
|
mentionIDs := make([]int64, id%6)
|
|
for i := range mentionIDs {
|
|
mentionIDs[i] = int64(i) + 1 // MentionID should not be 0
|
|
}
|
|
reviewedIDs := make([]int64, id%7)
|
|
for i := range reviewedIDs {
|
|
reviewedIDs[i] = int64(i) + 1 // ReviewID should not be 0
|
|
}
|
|
reviewRequestedIDs := make([]int64, id%8)
|
|
for i := range reviewRequestedIDs {
|
|
reviewRequestedIDs[i] = int64(i) + 1 // ReviewRequestedID should not be 0
|
|
}
|
|
subscriberIDs := make([]int64, id%9)
|
|
for i := range subscriberIDs {
|
|
subscriberIDs[i] = int64(i) + 1 // SubscriberID should not be 0
|
|
}
|
|
projectIDs := make([]int64, id%5)
|
|
for i := range projectIDs {
|
|
projectIDs[i] = int64(i) + 1 // projectID should not be 0
|
|
}
|
|
var assigneeIDs []int64
|
|
if issueIndex%10 != 0 {
|
|
assigneeID := issueIndex % 10
|
|
assigneeIDs = []int64{assigneeID}
|
|
if issueIndex%3 == 0 {
|
|
nextAssigneeID := assigneeID + 1
|
|
if nextAssigneeID == 10 {
|
|
nextAssigneeID = 1
|
|
}
|
|
assigneeIDs = append(assigneeIDs, nextAssigneeID)
|
|
}
|
|
}
|
|
|
|
data = append(data, &internal.IndexerData{
|
|
ID: id,
|
|
RepoID: repoID,
|
|
IsPublic: repoID%2 == 0,
|
|
Title: fmt.Sprintf("issue%d of repo%d", issueIndex, repoID),
|
|
Content: fmt.Sprintf("content%d", issueIndex),
|
|
Comments: comments,
|
|
IsPull: issueIndex%2 == 0,
|
|
IsClosed: issueIndex%3 == 0,
|
|
LabelIDs: labelIDs,
|
|
NoLabel: len(labelIDs) == 0,
|
|
MilestoneID: issueIndex % 4,
|
|
ProjectIDs: projectIDs,
|
|
NoProject: len(projectIDs) == 0,
|
|
PosterID: id%10 + 1, // PosterID should not be 0
|
|
AssigneeIDs: assigneeIDs,
|
|
NoAssignee: len(assigneeIDs) == 0,
|
|
MentionIDs: mentionIDs,
|
|
ReviewedIDs: reviewedIDs,
|
|
ReviewRequestedIDs: reviewRequestedIDs,
|
|
SubscriberIDs: subscriberIDs,
|
|
UpdatedUnix: timeutil.TimeStamp(id + issueIndex),
|
|
CreatedUnix: timeutil.TimeStamp(id),
|
|
DeadlineUnix: timeutil.TimeStamp(id + issueIndex + repoID),
|
|
CommentCount: int64(len(comments)),
|
|
})
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func countIndexerData(data map[int64]*internal.IndexerData, f func(v *internal.IndexerData) bool) int64 {
|
|
var count int64
|
|
for _, v := range data {
|
|
if f(v) {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// waitData waits for the indexer to index all data.
|
|
// Some engines like Elasticsearch index data asynchronously, so we need to wait for a while.
|
|
func waitData(t *testing.T, indexer internal.Indexer, total int64) {
|
|
if refresher, ok := indexer.(interface{ Refresh(context.Context) error }); ok {
|
|
require.NoError(t, refresher.Refresh(t.Context()))
|
|
}
|
|
assert.Eventually(t, func() bool {
|
|
result, err := indexer.Search(t.Context(), &internal.SearchOptions{Paginator: &db.ListOptions{}})
|
|
require.NoError(t, err)
|
|
return result.Total == total
|
|
}, 10*time.Second, 100*time.Millisecond, "expected total=%d", total)
|
|
}
|