package opensearch_test import ( "fmt" "strings" "testing" opensearchgo "github.com/opensearch-project/opensearch-go/v4" opensearchgoAPI "github.com/opensearch-project/opensearch-go/v4/opensearchapi" "github.com/stretchr/testify/require" searchService "github.com/qsfera/server/protogen/gen/qsfera/services/search/v0" "github.com/qsfera/server/services/search/pkg/opensearch" "github.com/qsfera/server/services/search/pkg/opensearch/internal/test" "github.com/qsfera/server/services/search/pkg/search" ) func TestNewBackend(t *testing.T) { t.Run("fails to create if the cluster is not healthy", func(t *testing.T) { client, err := opensearchgoAPI.NewClient(opensearchgoAPI.Config{ Client: opensearchgo.Config{ Addresses: []string{"http://localhost:1025"}, }, }) require.NoError(t, err, "failed to create OpenSearch client") backend, err := opensearch.NewBackend("test-engine-new-engine", client) require.Nil(t, backend) require.ErrorIs(t, err, opensearch.ErrUnhealthyCluster) }) } func TestEngine_Search(t *testing.T) { indexName := "qsfera-test-engine-search" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) document := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, document))) tc.Require.IndicesCount([]string{indexName}, nil, 1) t.Run("most simple search", func(t *testing.T) { resp, err := backend.Search(t.Context(), &searchService.SearchIndexRequest{ Query: fmt.Sprintf(`"%s"`, document.Name), }) require.NoError(t, err) require.Len(t, resp.Matches, 1) require.Equal(t, int32(1), resp.TotalMatches) require.Equal(t, document.ID, fmt.Sprintf("%s$%s!%s", resp.Matches[0].Entity.Id.StorageId, resp.Matches[0].Entity.Id.SpaceId, resp.Matches[0].Entity.Id.OpaqueId)) }) t.Run("ignores files that are marked as deleted", func(t *testing.T) { deletedDocument := opensearchtest.Testdata.Resources.File deletedDocument.ID = "1$2!4" deletedDocument.Deleted = true tc.Require.DocumentCreate(indexName, deletedDocument.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, deletedDocument))) tc.Require.IndicesCount([]string{indexName}, nil, 2) resp, err := backend.Search(t.Context(), &searchService.SearchIndexRequest{ Query: fmt.Sprintf(`"%s"`, document.Name), }) require.NoError(t, err) require.Len(t, resp.Matches, 1) require.Equal(t, int32(1), resp.TotalMatches) require.Equal(t, document.ID, fmt.Sprintf("%s$%s!%s", resp.Matches[0].Entity.Id.StorageId, resp.Matches[0].Entity.Id.SpaceId, resp.Matches[0].Entity.Id.OpaqueId)) }) } func TestEngine_Upsert(t *testing.T) { indexName := "qsfera-test-engine-upsert" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) t.Run("upsert with full document", func(t *testing.T) { document := opensearchtest.Testdata.Resources.File require.NoError(t, backend.Upsert(document.ID, document)) tc.Require.IndicesCount([]string{indexName}, nil, 1) }) } func TestEngine_Move(t *testing.T) { indexName := "qsfera-test-engine-move" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) t.Run("moves the document to a new path", func(t *testing.T) { document := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, document))) tc.Require.IndicesCount([]string{indexName}, nil, 1) body := opensearchtest.JSONMustMarshal(t, map[string]any{ "query": map[string]any{ "ids": map[string]any{ "values": []string{document.ID}, }, }, }) resources := opensearchtest.SearchHitsMustBeConverted[search.Resource](t, tc.Require.Search(indexName, strings.NewReader(body)).Hits) require.Len(t, resources, 1) require.Equal(t, document.Path, resources[0].Path) document.Path = "./new/path/to/resource" require.NoError(t, backend.Move(document.ID, document.ParentID, document.Path)) resources = opensearchtest.SearchHitsMustBeConverted[search.Resource](t, tc.Require.Search(indexName, strings.NewReader(body)).Hits) require.Len(t, resources, 1) require.Equal(t, document.Path, resources[0].Path) }) } func TestEngine_Delete(t *testing.T) { indexName := "qsfera-test-engine-delete" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) t.Run("mark document as deleted", func(t *testing.T) { document := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, document))) tc.Require.IndicesCount([]string{indexName}, nil, 1) body := opensearchtest.JSONMustMarshal(t, map[string]any{ "query": map[string]any{ "term": map[string]any{ "Deleted": map[string]any{ "value": true, }, }, }, }) tc.Require.IndicesCount([]string{indexName}, strings.NewReader(body), 0) require.NoError(t, backend.Delete(document.ID)) tc.Require.IndicesCount([]string{indexName}, strings.NewReader(body), 1) }) } func TestEngine_Restore(t *testing.T) { indexName := "qsfera-test-engine-restore" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) t.Run("mark document as not deleted", func(t *testing.T) { document := opensearchtest.Testdata.Resources.File document.Deleted = true tc.Require.DocumentCreate(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, document))) tc.Require.IndicesCount([]string{indexName}, nil, 1) body := opensearchtest.JSONMustMarshal(t, map[string]any{ "query": map[string]any{ "term": map[string]any{ "Deleted": map[string]any{ "value": true, }, }, }, }) tc.Require.IndicesCount([]string{indexName}, strings.NewReader(body), 1) require.NoError(t, backend.Restore(document.ID)) tc.Require.IndicesCount([]string{indexName}, strings.NewReader(body), 0) }) } func TestEngine_Purge(t *testing.T) { indexName := "qsfera-test-engine-purge" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) t.Run("purge with full document", func(t *testing.T) { document := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, document))) tc.Require.IndicesCount([]string{indexName}, nil, 1) require.NoError(t, backend.Purge(document.ID, false)) tc.Require.IndicesCount([]string{indexName}, nil, 0) }) t.Run("purge resource trees", func(t *testing.T) { resourceFolder := opensearchtest.Testdata.Resources.Folder tc.Require.DocumentCreate(indexName, resourceFolder.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, resourceFolder))) resourceFile := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, resourceFile.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, resourceFile))) tc.Require.IndicesCount([]string{indexName}, nil, 2) require.NoError(t, backend.Purge(resourceFolder.ID, false)) tc.Require.IndicesCount([]string{indexName}, nil, 0) }) t.Run("purge resource trees and ignores undeleted resources", func(t *testing.T) { resourceFolder := opensearchtest.Testdata.Resources.Folder tc.Require.DocumentCreate(indexName, resourceFolder.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, resourceFolder))) resourceFile := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, resourceFile.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, resourceFile))) tc.Require.IndicesCount([]string{indexName}, nil, 2) require.NoError(t, backend.Delete(resourceFile.ID)) tc.Require.IndicesRefresh([]string{indexName}, nil) require.NoError(t, backend.Purge(resourceFolder.ID, true)) tc.Require.IndicesCount([]string{indexName}, nil, 1) }) } func TestEngine_DocCount(t *testing.T) { indexName := "qsfera-test-engine-doc-count" tc := opensearchtest.NewDefaultTestClient(t, defaultConfig.Engine.OpenSearch.Client) tc.Require.IndicesReset([]string{indexName}) tc.Require.IndicesCount([]string{indexName}, nil, 0) defer tc.Require.IndicesDelete([]string{indexName}) backend, err := opensearch.NewBackend(indexName, tc.Client()) require.NoError(t, err) t.Run("ignore deleted documents", func(t *testing.T) { document := opensearchtest.Testdata.Resources.File tc.Require.DocumentCreate(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, document))) tc.Require.IndicesCount([]string{indexName}, nil, 1) count, err := backend.DocCount() require.NoError(t, err) require.Equal(t, uint64(1), count) tc.Require.Update(indexName, document.ID, strings.NewReader(opensearchtest.JSONMustMarshal(t, map[string]any{ "doc": map[string]any{ "Deleted": true, }, }))) tc.Require.IndicesCount([]string{indexName}, nil, 1) count, err = backend.DocCount() require.NoError(t, err) require.Equal(t, uint64(0), count) }) }