Files
search-go/integration/index_search_test.go

1341 lines
35 KiB
Go

package integration
import (
"crypto/tls"
"encoding/json"
"testing"
"github.com/hanzoai/search-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIndex_SearchWithContentEncoding(t *testing.T) {
tests := []struct {
Name string
ContentEncoding meilisearch.ContentEncoding
Query string
Request *meilisearch.SearchRequest
FacetRequest *meilisearch.FacetSearchRequest
Response *meilisearch.SearchResponse
FacetResponse *meilisearch.FacetSearchResponse
}{
{
Name: "SearchResultWithGzipEncoding",
ContentEncoding: meilisearch.GzipEncoding,
Query: "prince",
Request: &meilisearch.SearchRequest{
IndexUID: "indexUID",
Limit: 20,
Offset: 0,
},
FacetRequest: &meilisearch.FacetSearchRequest{
FacetName: "tag",
FacetQuery: "Novel",
},
FacetResponse: &meilisearch.FacetSearchResponse{
FacetHits: meilisearch.Hits{
{"value": json.RawMessage(`"Novel"`), "count": json.RawMessage(`5`)},
},
FacetQuery: "Novel",
},
Response: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"Tag": json.RawMessage(`"Epic fantasy"`), "book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
},
},
{
Name: "SearchResultWithDeflateEncoding",
ContentEncoding: meilisearch.DeflateEncoding,
Query: "prince",
Request: &meilisearch.SearchRequest{
IndexUID: "indexUID",
Limit: 20,
Offset: 0,
},
Response: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"Tag": json.RawMessage(`"Epic fantasy"`), "book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
},
FacetRequest: &meilisearch.FacetSearchRequest{
FacetName: "tag",
FacetQuery: "Novel",
},
FacetResponse: &meilisearch.FacetSearchResponse{
FacetHits: meilisearch.Hits{
{"value": json.RawMessage(`"Novel"`), "count": json.RawMessage(`5`)},
},
FacetQuery: "Novel",
},
},
{
Name: "SearchResultWithBrotliEncoding",
ContentEncoding: meilisearch.BrotliEncoding,
Query: "prince",
Request: &meilisearch.SearchRequest{
IndexUID: "indexUID",
Limit: 20,
Offset: 0,
},
Response: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"Tag": json.RawMessage(`"Epic fantasy"`), "book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
},
FacetRequest: &meilisearch.FacetSearchRequest{
FacetName: "tag",
FacetQuery: "Novel",
},
FacetResponse: &meilisearch.FacetSearchResponse{
FacetHits: meilisearch.Hits{
{"value": json.RawMessage(`"Novel"`), "count": json.RawMessage(`5`)},
},
FacetQuery: "Novel",
},
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
sv := setup(t, "", meilisearch.WithContentEncoding(tt.ContentEncoding, meilisearch.DefaultCompression))
setUpIndexForFaceting(sv)
i := sv.Index(tt.Request.IndexUID)
t.Cleanup(cleanup(sv))
require.NotNil(t, tt.Request)
require.NotEmpty(t, tt.Request.IndexUID)
got, err := i.Search(tt.Query, tt.Request)
require.NoError(t, err, "Search request failed unexpectedly")
require.Equal(t, len(tt.Response.Hits), len(got.Hits))
gotJson, err := i.SearchRaw(tt.Query, tt.Request)
require.NoError(t, err)
var resp meilisearch.SearchResponse
err = json.Unmarshal(*gotJson, &resp)
require.NoError(t, err, "error unmarshalling raw got meilisearch.SearchResponse")
require.Equal(t, len(tt.Response.Hits), len(resp.Hits))
filterableAttrs := []interface{}{"tag"}
task, err := i.UpdateFilterableAttributes(&filterableAttrs)
require.NoError(t, err)
testWaitForIndexTask(t, i, task)
gotJson, err = i.FacetSearch(tt.FacetRequest)
require.NoError(t, err)
var gotFacet meilisearch.FacetSearchResponse
err = json.Unmarshal(*gotJson, &gotFacet)
require.NoError(t, err, "error unmarshalling raw got meilisearch.FacetSearchResponse")
require.Equal(t, len(gotFacet.FacetHits), len(tt.FacetResponse.FacetHits))
})
}
}
func TestIndex_SearchRaw(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request *meilisearch.SearchRequest
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexBasicSearch",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: &meilisearch.SearchRequest{
IndexUID: "foobar",
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"Tag": json.RawMessage(`"Epic fantasy"`), "book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
{
name: "TestNullRequestInSearchRow",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: nil,
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
gotRaw, err := i.SearchRaw(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
// Unmarshal the raw response from SearchRaw into a meilisearch.SearchResponse
var got meilisearch.SearchResponse
err = json.Unmarshal(*gotRaw, &got)
require.NoError(t, err, "error unmarshalling raw got meilisearch.SearchResponse")
// Check meilisearch.Hits length
require.Equal(t, len(tt.want.Hits), len(got.Hits))
// Compare each hit in meilisearch.Hits
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["book_id"], actualHit["book_id"])
}
// Check if `_formatted` exists before comparison
if _, ok := tt.want.Hits[0]["_formatted"]; ok {
require.Equal(t, tt.want.Hits[0]["_formatted"], got.Hits[0]["_formatted"])
}
// Check other response fields
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.FacetDistribution, got.FacetDistribution)
})
}
}
func TestIndex_Search(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request *meilisearch.SearchRequest
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexSearchWithEmptyRequest",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: nil,
},
want: nil,
wantErr: true,
},
{
name: "TestIndexBasicSearch",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: &meilisearch.SearchRequest{},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"Tag": json.RawMessage(`"Epic fantasy"`), "book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
{
name: "TestIndexSearchWithLocale",
args: args{
UID: "indexUID",
client: sv,
query: "王子",
request: &meilisearch.SearchRequest{
Locales: []string{"jpn"},
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
map[string]json.RawMessage{
"book_id": toRawMessage(float64(1050)), "title": toRawMessage("星の王子さま"),
},
},
EstimatedTotalHits: 1,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
got, err := i.Search(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
require.Equal(t, len(tt.want.Hits), len(got.Hits))
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["book_id"], actualHit["book_id"])
}
if _, ok := tt.want.Hits[0]["_formatted"]; ok {
require.Equal(t, tt.want.Hits[0]["_formatted"], got.Hits[0]["_formatted"])
}
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.FacetDistribution, got.FacetDistribution)
})
}
}
func TestIndex_SearchFacets(t *testing.T) {
sv := setup(t, "")
customSv := setup(t, "", meilisearch.WithCustomClientWithTLS(&tls.Config{
InsecureSkipVerify: true,
}))
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request *meilisearch.SearchRequest
filterableAttributes []interface{}
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexSearchWithEmptyRequest",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: nil,
},
want: nil,
wantErr: true,
},
{
name: "TestIndexSearchWithFacets",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: &meilisearch.SearchRequest{
Facets: []string{"*"},
},
filterableAttributes: []interface{}{"tag"},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
FacetDistribution: toRawMessage(map[string]map[string]float64{
"tag": {
"Epic fantasy": 1,
"Tale": 1,
},
}),
},
wantErr: false,
},
{
name: "TestIndexSearchWithFacetsWithCustomClient",
args: args{
UID: "indexUID",
client: customSv,
query: "prince",
request: &meilisearch.SearchRequest{
Facets: []string{"*"},
},
filterableAttributes: []interface{}{"tag"},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
FacetDistribution: toRawMessage(map[string]map[string]float64{
"tag": {
"Epic fantasy": 1,
"Tale": 1,
},
}),
},
wantErr: false,
},
{
name: "TestIndexSearchWithFacetsAndFacetsStats",
args: args{
UID: "indexUID",
client: sv,
query: "prince",
request: &meilisearch.SearchRequest{
Facets: []string{"book_id"},
},
filterableAttributes: []interface{}{"book_id"},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`456`), "title": json.RawMessage(`"Le Petit Prince"`)},
{"book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 2,
Offset: 0,
Limit: 20,
FacetDistribution: toRawMessage(map[string]map[string]float64{
"book_id": {
"4": 1,
"456": 1,
},
}),
FacetStats: toRawMessage(map[string]map[string]float64{
"book_id": {
"max": 456,
"min": 4,
},
}),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
updateFilter, err := i.UpdateFilterableAttributes(&tt.args.filterableAttributes)
require.NoError(t, err)
testWaitForIndexTask(t, i, updateFilter)
got, err := i.Search(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
require.Equal(t, len(tt.want.Hits), len(got.Hits))
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["book_id"], actualHit["book_id"])
}
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.FacetDistribution, got.FacetDistribution)
if tt.want.FacetStats != nil {
require.NotNil(t, got.FacetStats)
}
})
}
}
func TestIndex_SearchWithFilters(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
filterableAttributes []interface{}
request *meilisearch.SearchRequest
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexBasicSearchWithFilter",
args: args{
UID: "indexUID",
client: sv,
query: "and",
filterableAttributes: []interface{}{"tag"},
request: &meilisearch.SearchRequest{
Filter: "tag = romance",
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`123`), "title": json.RawMessage(`"Pride and Prejudice"`)},
},
EstimatedTotalHits: 1,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
{
name: "TestIndexSearchWithFilterInInt",
args: args{
UID: "indexUID",
client: sv,
query: "and",
filterableAttributes: []interface{}{"year"},
request: &meilisearch.SearchRequest{
Filter: "year = 2005",
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": json.RawMessage(`4`), "title": json.RawMessage(`"Harry Potter and the Half-Blood Prince"`)},
},
EstimatedTotalHits: 1,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
updateFilter, err := i.UpdateFilterableAttributes(&tt.args.filterableAttributes)
require.NoError(t, err)
testWaitForIndexTask(t, i, updateFilter)
got, err := i.Search(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
require.Equal(t, len(tt.want.Hits), len(got.Hits))
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["book_id"], actualHit["book_id"])
}
require.Equal(t, tt.args.query, got.Query)
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.FacetDistribution, got.FacetDistribution)
})
}
}
func TestIndex_SearchWithSort(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
sortableAttributes []string
request *meilisearch.SearchRequest
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexBasicSearchWithSortIntParameter",
args: args{
UID: "indexUID",
client: sv,
query: "and",
sortableAttributes: []string{"year"},
request: &meilisearch.SearchRequest{
Sort: []string{"year:asc"},
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": toRawMessage(123), "title": toRawMessage("Pride and Prejudice")},
{"book_id": toRawMessage(730), "title": toRawMessage("War and Peace")},
{"book_id": toRawMessage(1032), "title": toRawMessage("Crime and Punishment")},
{"book_id": toRawMessage(4), "title": toRawMessage("Harry Potter and the Half-Blood Prince")},
},
EstimatedTotalHits: 4,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
updateFilter, err := i.UpdateSortableAttributes(&tt.args.sortableAttributes)
require.NoError(t, err)
testWaitForIndexTask(t, i, updateFilter)
got, err := i.Search(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
require.Equal(t, len(tt.want.Hits), len(got.Hits))
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["book_id"], actualHit["book_id"])
}
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.FacetDistribution, got.FacetDistribution)
})
}
}
func TestIndex_SearchOnNestedFields(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request *meilisearch.SearchRequest
searchableAttribute []string
sortableAttribute []string
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexBasicSearchOnNestedFields",
args: args{
UID: "TestIndexBasicSearchOnNestedFields",
client: sv,
query: "An awesome",
request: &meilisearch.SearchRequest{},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{
"id": toRawMessage(5), "title": toRawMessage("The Hobbit"),
"info": toRawMessage(map[string]interface{}{
"comment": "An awesome book", "reviewNb": 900,
}),
},
},
EstimatedTotalHits: 1,
Offset: 0,
Limit: 20,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexWithNestedFields(tt.args.client, tt.args.UID)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
if tt.args.searchableAttribute != nil {
gotTask, err := i.UpdateSearchableAttributes(&tt.args.searchableAttribute)
require.NoError(t, err)
testWaitForIndexTask(t, i, gotTask)
}
if tt.args.sortableAttribute != nil {
gotTask, err := i.UpdateSortableAttributes(&tt.args.sortableAttribute)
require.NoError(t, err)
testWaitForIndexTask(t, i, gotTask)
}
got, err := i.Search(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
require.Equal(t, len(tt.want.Hits), len(got.Hits))
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["id"], actualHit["id"])
require.Equal(t, expectedHit["info"], actualHit["info"])
}
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.FacetDistribution, got.FacetDistribution)
})
}
}
func TestIndex_SearchWithPagination(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request *meilisearch.SearchRequest
}
tests := []struct {
name string
args args
want *meilisearch.SearchResponse
wantErr bool
}{
{
name: "TestIndexBasicSearchWithHitsPerPage",
args: args{
UID: "indexUID",
client: sv,
query: "and",
request: &meilisearch.SearchRequest{
HitsPerPage: int64Ptr(10),
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": toRawMessage(123), "title": toRawMessage("Pride and Prejudice")},
{"book_id": toRawMessage(730), "title": toRawMessage("War and Peace")},
{"book_id": toRawMessage(1032), "title": toRawMessage("Crime and Punishment")},
{"book_id": toRawMessage(4), "title": toRawMessage("Harry Potter and the Half-Blood Prince")},
},
HitsPerPage: 10,
Page: 1,
TotalHits: 4,
TotalPages: 1,
},
wantErr: false,
},
{
name: "TestIndexBasicSearchWithPage",
args: args{
UID: "indexUID",
client: sv,
query: "and",
request: &meilisearch.SearchRequest{
Page: 1,
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": toRawMessage(123), "title": toRawMessage("Pride and Prejudice")},
{"book_id": toRawMessage(730), "title": toRawMessage("War and Peace")},
{"book_id": toRawMessage(1032), "title": toRawMessage("Crime and Punishment")},
{"book_id": toRawMessage(4), "title": toRawMessage("Harry Potter and the Half-Blood Prince")},
},
HitsPerPage: 20,
Page: 1,
TotalHits: 4,
TotalPages: 1,
},
wantErr: false,
},
{
name: "TestIndexBasicSearchWithPageAndHitsPerPage",
args: args{
UID: "indexUID",
client: sv,
query: "and",
request: &meilisearch.SearchRequest{
HitsPerPage: int64Ptr(10),
Page: 1,
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": toRawMessage(123), "title": toRawMessage("Pride and Prejudice")},
{"book_id": toRawMessage(730), "title": toRawMessage("War and Peace")},
{"book_id": toRawMessage(1032), "title": toRawMessage("Crime and Punishment")},
{"book_id": toRawMessage(4), "title": toRawMessage("Harry Potter and the Half-Blood Prince")},
},
HitsPerPage: 10,
Page: 1,
TotalHits: 4,
TotalPages: 1,
},
wantErr: false,
},
{
name: "TestIndexBasicSearchWithHitsPerPageZero",
args: args{
UID: "indexUID",
client: sv,
query: "and",
request: &meilisearch.SearchRequest{
HitsPerPage: int64Ptr(0),
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{},
HitsPerPage: 0,
Page: 1,
TotalHits: 4,
TotalPages: 0,
},
wantErr: false,
},
{
name: "TestIndexBasicSearchWithHitsPerPageNull",
args: args{
UID: "indexUID",
client: sv,
query: "and",
request: &meilisearch.SearchRequest{
HitsPerPage: nil,
},
},
want: &meilisearch.SearchResponse{
Hits: meilisearch.Hits{
{"book_id": toRawMessage(123), "title": toRawMessage("Pride and Prejudice")},
{"book_id": toRawMessage(730), "title": toRawMessage("War and Peace")},
{"book_id": toRawMessage(1032), "title": toRawMessage("Crime and Punishment")},
{"book_id": toRawMessage(4), "title": toRawMessage("Harry Potter and the Half-Blood Prince")},
},
HitsPerPage: 0,
Limit: 20,
Offset: 0,
EstimatedTotalHits: 4,
Page: 0,
TotalHits: 0,
TotalPages: 0,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
got, err := i.Search(tt.args.query, tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, tt.want)
return
}
require.NoError(t, err)
require.Equal(t, len(tt.want.Hits), len(got.Hits))
for idx := range got.Hits {
expectedHit := tt.want.Hits[idx]
actualHit := got.Hits[idx]
require.Equal(t, expectedHit["title"], actualHit["title"])
require.Equal(t, expectedHit["book_id"], actualHit["book_id"])
}
require.Equal(t, tt.args.query, got.Query)
require.Equal(t, tt.want.HitsPerPage, got.HitsPerPage)
require.Equal(t, tt.want.Page, got.Page)
require.Equal(t, tt.want.TotalHits, got.TotalHits)
require.Equal(t, tt.want.TotalPages, got.TotalPages)
require.Equal(t, tt.want.Limit, got.Limit)
require.Equal(t, tt.want.EstimatedTotalHits, got.EstimatedTotalHits)
require.Equal(t, tt.want.Offset, got.Offset)
})
}
}
func TestIndex_SearchWithShowRankingScore(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request meilisearch.SearchRequest
}
testArg := args{
UID: "indexUID",
client: sv,
query: "and",
request: meilisearch.SearchRequest{
ShowRankingScore: true,
},
}
setUpIndexForFaceting(testArg.client)
c := testArg.client
i := c.Index(testArg.UID)
t.Cleanup(cleanup(c))
got, err := i.Search(testArg.query, &testArg.request)
require.NoError(t, err)
require.NotNil(t, got)
// Ensure at least one result is present
require.Greater(t, len(got.Hits), 0)
// Convert to a structured format and verify _rankingScore presence
var result map[string]json.RawMessage
err = got.Hits[0].DecodeInto(&result)
require.NoError(t, err)
require.Contains(t, got.Hits[0], "_rankingScore")
}
func TestIndex_SearchWithShowRankingScoreDetails(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request meilisearch.SearchRequest
}
testArg := args{
UID: "indexUID",
client: sv,
query: "and",
request: meilisearch.SearchRequest{
ShowRankingScoreDetails: true,
},
}
setUpIndexForFaceting(testArg.client)
c := testArg.client
i := c.Index(testArg.UID)
t.Cleanup(cleanup(c))
got, err := i.Search(testArg.query, &testArg.request)
require.NoError(t, err)
require.NotNil(t, got)
require.Greater(t, len(got.Hits), 0, "expected at least one hit")
// Convert first hit to structured format and check for _rankingScoreDetails
var result map[string]json.RawMessage
err = got.Hits[0].DecodeInto(&result)
require.NoError(t, err)
require.Contains(t, result, "_rankingScoreDetails", "expected _rankingScoreDetails to be present in the search result")
}
func TestIndex_SearchWithVectorStore(t *testing.T) {
sv := setup(t, "")
tests := []struct {
name string
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request meilisearch.SearchRequest
}{
{
name: "basic hybrid test",
UID: "indexUID",
client: sv,
query: "Pride and Prejudice",
request: meilisearch.SearchRequest{
Hybrid: &meilisearch.SearchRequestHybrid{
SemanticRatio: 0.5,
Embedder: "default",
},
RetrieveVectors: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i, err := setUpIndexWithVector(tt.client, tt.UID)
require.NoError(t, err)
c := tt.client
t.Cleanup(cleanup(c))
got, err := i.Search(tt.query, &tt.request)
require.NoError(t, err)
require.Greater(t, len(got.Hits), 0, "expected at least one hit")
for _, hit := range got.Hits {
var hitMap map[string]json.RawMessage
err := hit.DecodeInto(&hitMap)
require.NoError(t, err)
require.Contains(t, hitMap, "_vectors", "expected _vectors field in search result")
}
})
}
}
func TestIndex_SearchWithDistinct(t *testing.T) {
sv := setup(t, "")
tests := []struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
query string
request meilisearch.SearchRequest
}{
{
UID: "indexUID",
client: sv,
query: "white shirt",
request: meilisearch.SearchRequest{
Distinct: "sku",
},
},
}
for _, tt := range tests {
t.Run(tt.UID, func(t *testing.T) {
setUpDistinctIndex(tt.client, tt.UID)
c := tt.client
t.Cleanup(cleanup(c))
i := c.Index(tt.UID)
got, err := i.Search(tt.query, &tt.request)
require.NoError(t, err)
require.NotNil(t, got.Hits)
})
}
}
func TestIndex_SearchSimilarDocuments(t *testing.T) {
sv := setup(t, "")
tests := []struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
request *meilisearch.SimilarDocumentQuery
resp *meilisearch.SimilarDocumentResult
wantErr bool
}{
{
UID: "indexUID",
client: sv,
request: &meilisearch.SimilarDocumentQuery{
Id: "123",
Embedder: "default",
},
resp: new(meilisearch.SimilarDocumentResult),
wantErr: false,
},
{
UID: "indexUID",
client: sv,
request: &meilisearch.SimilarDocumentQuery{
Embedder: "default",
},
resp: new(meilisearch.SimilarDocumentResult),
wantErr: true,
},
{
UID: "indexUID",
client: sv,
request: &meilisearch.SimilarDocumentQuery{
Id: "123",
Embedder: "default",
ShowPerformanceDetails: true,
},
resp: new(meilisearch.SimilarDocumentResult),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.UID, func(t *testing.T) {
i, err := setUpIndexWithVector(tt.client, tt.UID)
require.NoError(t, err)
c := tt.client
t.Cleanup(cleanup(c))
err = i.SearchSimilarDocuments(tt.request, tt.resp)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, tt.resp)
if tt.request.ShowPerformanceDetails {
require.NotNil(t, tt.resp.PerformanceDetails)
}
})
}
}
func TestIndex_FacetSearch(t *testing.T) {
sv := setup(t, "")
type args struct {
UID string
PrimaryKey string
client meilisearch.ServiceManager
request *meilisearch.FacetSearchRequest
filterableAttributes []interface{}
}
tests := []struct {
name string
args args
want *meilisearch.FacetSearchResponse
wantErr bool
}{
{
name: "TestIndexBasicFacetSearch",
args: args{
UID: "indexUID",
client: sv,
request: &meilisearch.FacetSearchRequest{
FacetName: "tag",
FacetQuery: "Novel",
},
filterableAttributes: []interface{}{"tag"},
},
want: &meilisearch.FacetSearchResponse{
FacetHits: meilisearch.Hits{
{"value": toRawMessage("Novel"), "count": toRawMessage(5)},
},
FacetQuery: "Novel",
},
wantErr: false,
},
{
name: "TestIndexFacetSearchWithNoFacetSearchRequest",
args: args{
UID: "indexUID",
client: sv,
request: nil,
},
want: nil,
wantErr: true,
},
{
name: "TestIndexFacetSearchWithNoFacetName",
args: args{
UID: "indexUID",
client: sv,
request: &meilisearch.FacetSearchRequest{
FacetQuery: "Novel",
},
},
want: nil,
wantErr: true,
},
{
name: "TestIndexFacetSearchWithQ",
args: args{
UID: "indexUID",
client: sv,
request: &meilisearch.FacetSearchRequest{
Q: "query",
FacetName: "tag",
},
filterableAttributes: []interface{}{"tag"},
},
want: &meilisearch.FacetSearchResponse{
FacetHits: meilisearch.Hits{},
FacetQuery: "",
},
wantErr: false,
},
{
name: "TestIndexFacetSearchWithFilterArray",
args: args{
UID: "indexUID",
client: sv,
request: &meilisearch.FacetSearchRequest{
FacetName: "tag",
FacetQuery: "Novel",
Filter: []string{"tag = Novel"},
},
filterableAttributes: []interface{}{"tag"},
},
want: &meilisearch.FacetSearchResponse{
FacetHits: meilisearch.Hits{
{"value": toRawMessage("Novel"), "count": toRawMessage(5)},
},
FacetQuery: "Novel",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpIndexForFaceting(tt.args.client)
c := tt.args.client
i := c.Index(tt.args.UID)
t.Cleanup(cleanup(c))
if len(tt.args.filterableAttributes) > 0 {
updateFilter, err := i.UpdateFilterableAttributes(&tt.args.filterableAttributes)
require.NoError(t, err)
testWaitForIndexTask(t, i, updateFilter)
}
gotRaw, err := i.FacetSearch(tt.args.request)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, gotRaw)
return
}
require.NoError(t, err)
require.NotNil(t, gotRaw)
// Unmarshal the raw response into a meilisearch.FacetSearchResponse
var got meilisearch.FacetSearchResponse
err = json.Unmarshal(*gotRaw, &got)
require.NoError(t, err, "error unmarshalling raw got meilisearch.FacetSearchResponse")
require.Equal(t, len(tt.want.FacetHits), len(got.FacetHits))
for idx := range got.FacetHits {
expectedHit := tt.want.FacetHits[idx]
actualHit := got.FacetHits[idx]
require.Equal(t, expectedHit["value"], actualHit["value"])
require.Equal(t, expectedHit["count"], actualHit["count"])
}
require.Equal(t, tt.want.FacetQuery, got.FacetQuery)
})
}
}
func TestIndex_ShowPerformanceDetails(t *testing.T) {
sv := setup(t, "")
t.Cleanup(cleanup(sv))
setUpIndexForFaceting(sv)
idx := sv.Index("indexUID")
resp, err := idx.Search("Pride", &meilisearch.SearchRequest{
ShowPerformanceDetails: true,
})
require.NoError(t, err)
require.NotNil(t, resp)
assert.NotNil(t, resp.PerformanceDetails)
}