Signed-off-by: Thomas Miceli <tho.miceli@gmail.com> Co-authored-by: Qiang Zhou <zhouqiang.loaded@bytedance.com> Co-authored-by: theodoruszq <theodoruszq@gmail.com>
782 lines
25 KiB
Go
782 lines
25 KiB
Go
package index
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newGist creates a Gist with sensible defaults for testing.
|
|
func newGist(id uint, userID uint, visibility uint, content string) *Gist {
|
|
return &Gist{
|
|
GistID: id,
|
|
UserID: userID,
|
|
Visibility: visibility,
|
|
Username: fmt.Sprintf("user%d", userID),
|
|
Title: fmt.Sprintf("Gist %d", id),
|
|
Content: content,
|
|
Filenames: []string{"file.txt"},
|
|
Extensions: []string{"txt"},
|
|
Languages: []string{"Text"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890,
|
|
UpdatedAt: 1234567890,
|
|
}
|
|
}
|
|
|
|
func testAddAndSearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
t.Run("add and search by content", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 0, "the quick brown fox jumps over the lazy dog")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "fox"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("add nil gist returns error", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
require.Error(t, indexer.Add(nil))
|
|
})
|
|
|
|
t.Run("add then remove", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 0, "removable content here")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "removable"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
|
|
require.NoError(t, indexer.Remove(1))
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "removable"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total)
|
|
})
|
|
|
|
t.Run("update gist replaces content", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 0, "original content alpha")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
g2 := newGist(1, 1, 0, "updated content beta")
|
|
require.NoError(t, indexer.Add(g2))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "alpha"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total, "old content should not be found")
|
|
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "beta"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("gist with empty optional fields still searchable by content", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "user1", Title: "",
|
|
Content: "searchable bare content",
|
|
Filenames: []string{},
|
|
Extensions: []string{},
|
|
Languages: []string{},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "searchable"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
}
|
|
|
|
func testAccessControl(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
t.Run("public gist visible to any user", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 0, "public content")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "public"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total, "owner should see public gist")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "public"}, 99, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total, "other user should see public gist")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "public"}, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total, "anonymous should see public gist")
|
|
})
|
|
|
|
t.Run("private gist only visible to owner", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 1, "private secret")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "secret"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total, "owner should see private gist")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "secret"}, 99, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total, "other user should not see private gist")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "secret"}, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total, "anonymous should not see private gist")
|
|
})
|
|
|
|
t.Run("unlisted gist only visible to owner", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 2, "unlisted hidden")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "hidden"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total, "owner should see unlisted gist")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "hidden"}, 99, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total, "other user should not see unlisted gist")
|
|
})
|
|
|
|
t.Run("mixed visibility correct counts per user", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g1 := newGist(1, 1, 0, "alphaword content")
|
|
g2 := newGist(2, 1, 1, "alphaword content")
|
|
g3 := newGist(3, 2, 0, "alphaword content")
|
|
g4 := newGist(4, 2, 1, "alphaword content")
|
|
|
|
for _, g := range []*Gist{g1, g2, g3, g4} {
|
|
require.NoError(t, indexer.Add(g))
|
|
}
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "alphaword"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(3), total, "user1 sees 2 public + 1 own private")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "alphaword"}, 2, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(3), total, "user2 sees 2 public + 1 own private")
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "alphaword"}, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(2), total, "anonymous sees only public")
|
|
})
|
|
}
|
|
|
|
func testMetadataFilters(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g1 := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "alice", Title: "Go Tutorial",
|
|
Description: "A helper utility for parsing JSON",
|
|
Content: "learning golang basics",
|
|
Filenames: []string{"main.go"},
|
|
Extensions: []string{"go"},
|
|
Languages: []string{"Go"},
|
|
Topics: []string{"tutorial"},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
g2 := &Gist{
|
|
GistID: 2, UserID: 2, Visibility: 0,
|
|
Username: "bob", Title: "Python Script",
|
|
Description: "Database migration scripts",
|
|
Content: "learning python basics",
|
|
Filenames: []string{"script.py"},
|
|
Extensions: []string{"py"},
|
|
Languages: []string{"Python"},
|
|
Topics: []string{"scripting"},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
|
|
for _, g := range []*Gist{g1, g2} {
|
|
require.NoError(t, indexer.Add(g))
|
|
}
|
|
|
|
t.Run("filter by Username", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Username: "alice"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("filter by Language", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Language: "Python"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("filter by Extension with dot prefix", func(t *testing.T) {
|
|
g3 := &Gist{
|
|
GistID: 3, UserID: 1, Visibility: 0,
|
|
Username: "alice", Title: "Dot Extension Test",
|
|
Content: "extension test content",
|
|
Filenames: []string{"test.rs"},
|
|
Extensions: []string{".rs"},
|
|
Languages: []string{"Rust"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
require.NoError(t, indexer.Add(g3))
|
|
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "extension", Extension: "rs"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("filter by Topic", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Topic: "tutorial"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("filter by Filename", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Filename: "script.py"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("filter by Title", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Title: "Go Tutorial"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("filter by Description", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Description: "parsing"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("combined filters narrow results", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Language: "Go", Username: "alice"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "learning", Language: "Python", Username: "alice"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total, "mismatched filters should return 0")
|
|
})
|
|
|
|
t.Run("filter matches no gists", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "learning", Username: "nonexistent"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total)
|
|
})
|
|
}
|
|
|
|
func testAllFieldSearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "alice", Title: "MyTitle",
|
|
Description: "Database migration scripts",
|
|
Content: "somecontent",
|
|
Filenames: []string{"readme.md"},
|
|
Extensions: []string{".md"},
|
|
Languages: []string{"Markdown"},
|
|
Topics: []string{"documentation"},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
g2 := newGist(2, 2, 0, "unrelated other stuff")
|
|
require.NoError(t, indexer.Add(g2))
|
|
|
|
t.Run("All matches username", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{All: "alice"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("All matches title", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{All: "MyTitle"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("All matches description", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{All: "migration"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("All matches language", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{All: "Markdown"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("All matches topic", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{All: "documentation"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("All matches filename", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{All: "readme.md"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("All ignores specific filters", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{All: "alice", Username: "nonexistent"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total, "All should take precedence over specific filters")
|
|
})
|
|
|
|
t.Run("All combined with content query", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "somecontent", All: "alice"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
|
|
_, total, _, err = indexer.Search(SearchGistMetadata{Content: "somecontent", All: "nonexistent"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total, "All not matching should yield 0")
|
|
})
|
|
}
|
|
|
|
func testFuzzySearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 0, "the elephant danced gracefully")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
t.Run("exact match", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "elephant"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("1-char substitution", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "elephent"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("1-char deletion", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "elepant"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("1-char insertion", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "elephantz"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("character transposition", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "elehpant"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("case insensitive", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "ELEPHANT"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("empty content query with metadata returns MatchAll", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
}
|
|
|
|
func testContentSearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "user1", Title: "Kubernetes Deployment Helper",
|
|
Content: `// café résumé naïve
|
|
func getHTTPResponse(url string) {
|
|
xmlParser := NewXMLParser()
|
|
myFunctionName := calculate(x, y)
|
|
fmt.Println("hello world")
|
|
self.cpuCard = initCard()
|
|
// the quick brown fox jumps over the lazy dog
|
|
elephant := fetchData()
|
|
if result == 0 {
|
|
return
|
|
}
|
|
}`,
|
|
Filenames: []string{"file.txt"},
|
|
Extensions: []string{"txt"},
|
|
Languages: []string{"Text"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
t.Run("code content/calculate", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "calculate"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("code content/Println", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "Println"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("code content/hello", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "hello"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("multi-word/both present", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "fox lazy"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("multi-word/one missing", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "fox unicorn"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total)
|
|
})
|
|
|
|
t.Run("prefix/content eleph", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "eleph"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("prefix/title Kube", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Title: "Kube"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("unicode/café", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "café"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("unicode/résumé", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "résumé"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("unicode/normalization cafe", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "cafe"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
})
|
|
|
|
t.Run("camelCase/function", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "function"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("camelCase/xml", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "xml"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("camelCase/parser", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "parser"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("camelCase/http", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "http"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("camelCase/response", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "response"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("camelCase/cpucard", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "cpucard"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
}
|
|
|
|
func testPagination(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
for i := uint(1); i <= 25; i++ {
|
|
g := newGist(i, 1, 0, "pagination keyword content")
|
|
require.NoError(t, indexer.Add(g))
|
|
}
|
|
|
|
t.Run("page sizes", func(t *testing.T) {
|
|
ids1, total1, _, err := indexer.Search(SearchGistMetadata{Content: "pagination"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(25), total1)
|
|
require.GreaterOrEqual(t, len(ids1), 10, "page 1 should have at least 10 results")
|
|
|
|
ids2, total2, _, err := indexer.Search(SearchGistMetadata{Content: "pagination"}, 1, 2)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(25), total2)
|
|
require.GreaterOrEqual(t, len(ids2), 10, "page 2 should have at least 10 results")
|
|
|
|
ids3, _, _, err := indexer.Search(SearchGistMetadata{Content: "pagination"}, 1, 3)
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(ids3), 5, "page 3 should have at least 5 results")
|
|
})
|
|
|
|
t.Run("no duplicates between pages", func(t *testing.T) {
|
|
ids1, _, _, err := indexer.Search(SearchGistMetadata{Content: "pagination"}, 1, 1)
|
|
require.NoError(t, err)
|
|
ids2, _, _, err := indexer.Search(SearchGistMetadata{Content: "pagination"}, 1, 2)
|
|
require.NoError(t, err)
|
|
|
|
page1 := ids1
|
|
if len(page1) > 10 {
|
|
page1 = page1[:10]
|
|
}
|
|
page2 := ids2
|
|
if len(page2) > 10 {
|
|
page2 = page2[:10]
|
|
}
|
|
|
|
seen := make(map[uint]bool)
|
|
for _, id := range page1 {
|
|
seen[id] = true
|
|
}
|
|
for _, id := range page2 {
|
|
require.False(t, seen[id], "duplicate gist ID %d found across pages", id)
|
|
}
|
|
})
|
|
|
|
t.Run("out of bounds page returns 0 results", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "pagination"}, 1, 100)
|
|
require.NoError(t, err)
|
|
require.Empty(t, ids)
|
|
require.Equal(t, uint64(25), total, "total should still reflect actual count")
|
|
})
|
|
}
|
|
|
|
func testLanguageFacets(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
t.Run("facets reflect language counts", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
languages := []string{"Go", "Go", "Python", "JavaScript"}
|
|
for i, lang := range languages {
|
|
g := newGist(uint(i+1), 1, 0, "facet test content")
|
|
g.Languages = []string{lang}
|
|
require.NoError(t, indexer.Add(g))
|
|
}
|
|
|
|
_, _, facets, err := indexer.Search(SearchGistMetadata{Content: "facet"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, facets["go"])
|
|
require.Equal(t, 1, facets["python"])
|
|
require.Equal(t, 1, facets["javascript"])
|
|
})
|
|
|
|
t.Run("facets respect visibility", func(t *testing.T) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g1 := newGist(1, 1, 0, "facet visibility test")
|
|
g1.Languages = []string{"Go"}
|
|
g2 := newGist(2, 1, 1, "facet visibility test")
|
|
g2.Languages = []string{"Rust"}
|
|
|
|
for _, g := range []*Gist{g1, g2} {
|
|
require.NoError(t, indexer.Add(g))
|
|
}
|
|
|
|
_, _, facets, err := indexer.Search(SearchGistMetadata{Content: "facet"}, 99, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, facets["go"])
|
|
require.Equal(t, 0, facets["rust"])
|
|
|
|
_, _, facets, err = indexer.Search(SearchGistMetadata{Content: "facet"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, facets["go"])
|
|
require.Equal(t, 1, facets["rust"])
|
|
})
|
|
}
|
|
|
|
func testWildcardSearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := newGist(1, 1, 0, "the elephant danced gracefully")
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
t.Run("substring wildcard match on content", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Content: "leph"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("substring wildcard no match", func(t *testing.T) {
|
|
_, total, _, err := indexer.Search(SearchGistMetadata{Content: "zzzz"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), total)
|
|
})
|
|
}
|
|
|
|
func testMetadataOnlySearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g1 := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "alice", Title: "Go Tutorial",
|
|
Content: "learning golang basics",
|
|
Filenames: []string{"main.go"},
|
|
Extensions: []string{"go"},
|
|
Languages: []string{"Go"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
g2 := &Gist{
|
|
GistID: 2, UserID: 2, Visibility: 0,
|
|
Username: "bob", Title: "Python Script",
|
|
Content: "data processing pipeline",
|
|
Filenames: []string{"script.py"},
|
|
Extensions: []string{"py"},
|
|
Languages: []string{"Python"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
|
|
for _, g := range []*Gist{g1, g2} {
|
|
require.NoError(t, indexer.Add(g))
|
|
}
|
|
|
|
t.Run("Username only no content", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Username: "alice"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("Language only no content", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Language: "Python"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(2), ids[0])
|
|
})
|
|
|
|
t.Run("Title only no content", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Title: "Go Tutorial"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
}
|
|
|
|
func testTitleFuzzySearch(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "alice", Title: "Kubernetes Deployment",
|
|
Content: "some content",
|
|
Filenames: []string{"deploy.yaml"},
|
|
Extensions: []string{"yaml"},
|
|
Languages: []string{"YAML"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
t.Run("title with typo still matches", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Title: "Kuberntes"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
|
|
t.Run("title prefix matches", func(t *testing.T) {
|
|
ids, total, _, err := indexer.Search(SearchGistMetadata{Title: "Kube"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), total)
|
|
require.Equal(t, uint(1), ids[0])
|
|
})
|
|
}
|
|
|
|
func testMultiLanguageFacets(t *testing.T, setup func(t *testing.T) (Indexer, func())) {
|
|
indexer, cleanup := setup(t)
|
|
defer cleanup()
|
|
|
|
g := &Gist{
|
|
GistID: 1, UserID: 1, Visibility: 0,
|
|
Username: "alice", Title: "Multi-lang gist",
|
|
Content: "polyglot content",
|
|
Filenames: []string{"main.go", "script.py"},
|
|
Extensions: []string{"go", "py"},
|
|
Languages: []string{"Go", "Python"},
|
|
Topics: []string{},
|
|
CreatedAt: 1234567890, UpdatedAt: 1234567890,
|
|
}
|
|
require.NoError(t, indexer.Add(g))
|
|
|
|
_, _, facets, err := indexer.Search(SearchGistMetadata{Content: "polyglot"}, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, facets["go"], "Go should appear in facets")
|
|
require.Equal(t, 1, facets["python"], "Python should appear in facets")
|
|
}
|