mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-12 21:58:58 -04:00
test(search): port fieldindex, mimetype and normalize tests to ginkgo
This commit is contained in:
1 parent
65712716a8
commit
f40544de04
5 files changed
+270
-235
No files matched your search
@@ -3,15 +3,15 @@ package mapping_test
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/mapping"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
)
|
||||
|
||||
func resourceFieldIndex(testing.TB) map[string]string {
|
||||
func resourceFieldIndex() map[string]string {
|
||||
return mapping.FieldNameIndex(
|
||||
reflect.TypeFor[search.Resource](),
|
||||
search.Resource{}.SearchFieldOverrides(),
|
||||
@@ -26,58 +26,6 @@ func resolve(idx map[string]string, key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
func TestFieldNameIndex_TopLevelCaseInsensitive(t *testing.T) {
|
||||
idx := resourceFieldIndex(t)
|
||||
for in, want := range map[string]string{
|
||||
"rootid": "RootID", "ROOTID": "RootID", "RootID": "RootID",
|
||||
"name": "Name", "NAME": "Name",
|
||||
"mimetype": "MimeType", "MimeType": "MimeType",
|
||||
"tags": "Tags", "favorites": "Favorites",
|
||||
"mtime": "Mtime", "parentid": "ParentID", "id": "ID",
|
||||
} {
|
||||
require.Equalf(t, want, resolve(idx, in), "resolve(%q)", in)
|
||||
}
|
||||
}
|
||||
|
||||
// Facet sub-fields are lowerCamelCase in the index (from libregraph json tags);
|
||||
// the derived index resolves them case-insensitively.
|
||||
func TestFieldNameIndex_FacetsCaseInsensitive(t *testing.T) {
|
||||
idx := resourceFieldIndex(t)
|
||||
for in, want := range map[string]string{
|
||||
// case-insensitive: same field, different casings
|
||||
"photo.cameramake": "photo.cameraMake",
|
||||
"photo.CAMERAMAKE": "photo.cameraMake",
|
||||
// a representative sub-field across each facet
|
||||
"photo.takendatetime": "photo.takenDateTime",
|
||||
"audio.artist": "audio.artist",
|
||||
"audio.albumartist": "audio.albumArtist",
|
||||
"image.width": "image.width",
|
||||
"location.latitude": "location.latitude",
|
||||
} {
|
||||
require.Equalf(t, want, resolve(idx, in), "resolve(%q)", in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldNameIndex_UnknownPassesThrough(t *testing.T) {
|
||||
idx := resourceFieldIndex(t)
|
||||
require.Equal(t, "nope.field", resolve(idx, "nope.field"))
|
||||
require.Equal(t, "custom", resolve(idx, "custom"))
|
||||
}
|
||||
|
||||
// All top-level fields are covered from one derived source, so both backends
|
||||
// resolve them the same way.
|
||||
func TestFieldNameIndex_CoversAllTopLevelFields(t *testing.T) {
|
||||
idx := resourceFieldIndex(t)
|
||||
for in, want := range map[string]string{
|
||||
"rootid": "RootID", "path": "Path", "id": "ID", "name": "Name",
|
||||
"size": "Size", "mtime": "Mtime", "type": "Type",
|
||||
"content": "Content", "hidden": "Hidden", "tags": "Tags",
|
||||
"favorites": "Favorites",
|
||||
} {
|
||||
require.Equalf(t, want, resolve(idx, in), "derived should cover %q", in)
|
||||
}
|
||||
}
|
||||
|
||||
// NestInner is embedded with a json tag below, so it must nest, not flatten.
|
||||
type NestInner struct {
|
||||
A string `json:"A"`
|
||||
@@ -88,11 +36,66 @@ type taggedOuter struct {
|
||||
Top string `json:"top"`
|
||||
}
|
||||
|
||||
// A json-tagged embedded struct nests under its tag in the derived index too
|
||||
// (walkFields must match encoding/json), so its fields are "inner.A", not "A".
|
||||
func TestFieldNameIndex_TaggedEmbeddedNests(t *testing.T) {
|
||||
idx := mapping.FieldNameIndex(reflect.TypeFor[taggedOuter](), nil)
|
||||
require.Equal(t, "inner.A", resolve(idx, "inner.a")) // nested under the tag
|
||||
require.Equal(t, "top", resolve(idx, "top"))
|
||||
require.Equal(t, "a", resolve(idx, "a")) // not flattened: bare "a" is not a key
|
||||
}
|
||||
var _ = Describe("FieldNameIndex", func() {
|
||||
It("resolves top-level fields case-insensitively", func() {
|
||||
idx := resourceFieldIndex()
|
||||
for in, want := range map[string]string{
|
||||
"rootid": "RootID", "ROOTID": "RootID", "RootID": "RootID",
|
||||
"name": "Name", "NAME": "Name",
|
||||
"mimetype": "MimeType", "MimeType": "MimeType",
|
||||
"tags": "Tags", "favorites": "Favorites",
|
||||
"mtime": "Mtime", "parentid": "ParentID", "id": "ID",
|
||||
} {
|
||||
Expect(resolve(idx, in)).To(Equal(want), "resolve(%q)", in)
|
||||
}
|
||||
})
|
||||
|
||||
// Facet sub-fields are lowerCamelCase in the index (from libregraph json
|
||||
// tags); the derived index resolves them case-insensitively.
|
||||
It("resolves facet sub-fields case-insensitively", func() {
|
||||
idx := resourceFieldIndex()
|
||||
for in, want := range map[string]string{
|
||||
// case-insensitive: same field, different casings
|
||||
"photo.cameramake": "photo.cameraMake",
|
||||
"photo.CAMERAMAKE": "photo.cameraMake",
|
||||
// a representative sub-field across each facet
|
||||
"photo.takendatetime": "photo.takenDateTime",
|
||||
"audio.artist": "audio.artist",
|
||||
"audio.albumartist": "audio.albumArtist",
|
||||
"image.width": "image.width",
|
||||
"location.latitude": "location.latitude",
|
||||
} {
|
||||
Expect(resolve(idx, in)).To(Equal(want), "resolve(%q)", in)
|
||||
}
|
||||
})
|
||||
|
||||
It("passes unknown keys through unchanged", func() {
|
||||
idx := resourceFieldIndex()
|
||||
Expect(resolve(idx, "nope.field")).To(Equal("nope.field"))
|
||||
Expect(resolve(idx, "custom")).To(Equal("custom"))
|
||||
})
|
||||
|
||||
// All top-level fields are covered from one derived source, so both
|
||||
// backends resolve them the same way.
|
||||
It("covers all top-level fields", func() {
|
||||
idx := resourceFieldIndex()
|
||||
for in, want := range map[string]string{
|
||||
"rootid": "RootID", "path": "Path", "id": "ID", "name": "Name",
|
||||
"size": "Size", "mtime": "Mtime", "type": "Type",
|
||||
"content": "Content", "hidden": "Hidden", "tags": "Tags",
|
||||
"favorites": "Favorites",
|
||||
} {
|
||||
Expect(resolve(idx, in)).To(Equal(want), "derived should cover %q", in)
|
||||
}
|
||||
})
|
||||
|
||||
// A json-tagged embedded struct nests under its tag in the derived index
|
||||
// too (walkFields must match encoding/json), so its fields are "inner.A",
|
||||
// not "A".
|
||||
It("nests json-tagged embedded structs", func() {
|
||||
idx := mapping.FieldNameIndex(reflect.TypeFor[taggedOuter](), nil)
|
||||
Expect(resolve(idx, "inner.a")).To(Equal("inner.A")) // nested under the tag
|
||||
Expect(resolve(idx, "top")).To(Equal("top"))
|
||||
Expect(resolve(idx, "a")).To(Equal("a")) // not flattened: bare "a" is not a key
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
package mimetype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestMimetype(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Mimetype Suite")
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package mimetype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/ast"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/query/mimetype"
|
||||
@@ -12,93 +11,6 @@ import (
|
||||
// This is the single place the mediatype -> MimeType mapping is tested. The
|
||||
// query pipeline consumes Expand via query.Normalize and must NOT re-test it.
|
||||
|
||||
func TestExpand_onlyTriggersOnMediatype(t *testing.T) {
|
||||
require.Nil(t, mimetype.Expand("Name", "document"))
|
||||
require.Nil(t, mimetype.Expand("MimeType", "file")) // the real field name is not the trigger
|
||||
require.Nil(t, mimetype.Expand("Tags", "file"))
|
||||
}
|
||||
|
||||
func TestExpand_keyIsCaseInsensitive(t *testing.T) {
|
||||
require.NotNil(t, mimetype.Expand("MediaType", "file"))
|
||||
}
|
||||
|
||||
func TestExpand_valueIsCaseInsensitive(t *testing.T) {
|
||||
// a category matches regardless of case
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}, mimetype.Expand("mediatype", "Folder"))
|
||||
// a literal MIME type is lowercased too (MIME types are case-insensitive)
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "image/svg+xml"},
|
||||
}, mimetype.Expand("mediatype", "Image/SVG+XML"))
|
||||
}
|
||||
|
||||
// A non-category value is a literal MIME type and targets the MimeType field.
|
||||
func TestExpand_literalValuePassesThroughToMimeType(t *testing.T) {
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "application/pdf"},
|
||||
}, mimetype.Expand("mediatype", "application/pdf"))
|
||||
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "image/jpeg"},
|
||||
}, mimetype.Expand("mediatype", "image/jpeg"))
|
||||
}
|
||||
|
||||
func TestExpand_fileIsNotAFolder(t *testing.T) {
|
||||
// grouped so the negation stays atomic next to an operator.
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.OperatorNode{Value: "NOT"},
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}},
|
||||
}, mimetype.Expand("mediatype", "file"))
|
||||
}
|
||||
|
||||
func TestExpand_folderIsASingleTerm(t *testing.T) {
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}, mimetype.Expand("mediatype", "folder"))
|
||||
}
|
||||
|
||||
func TestExpand_wildcardCategories(t *testing.T) {
|
||||
for value, mime := range map[string]string{
|
||||
"image": "image/*", "video": "video/*", "audio": "audio/*", "pdf": "application/pdf",
|
||||
} {
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: mime},
|
||||
}, mimetype.Expand("mediatype", value), value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpand_documentGroup(t *testing.T) {
|
||||
got := mimetype.Expand("mediatype", "document")
|
||||
require.Len(t, got, 1)
|
||||
group, ok := got[0].(*ast.GroupNode)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, mimeValues(group), []string{
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.form",
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"text/plain",
|
||||
"text/markdown",
|
||||
"application/rtf",
|
||||
"application/vnd.apple.pages",
|
||||
})
|
||||
}
|
||||
|
||||
// spreadsheet asserts the exact MIME set, in order, with no duplicate entry.
|
||||
func TestExpand_spreadsheet(t *testing.T) {
|
||||
group := mimetype.Expand("mediatype", "spreadsheet")[0].(*ast.GroupNode)
|
||||
require.Equal(t, mimeValues(group), []string{
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.oasis.opendocument.spreadsheet",
|
||||
"text/csv",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.apple.numbers",
|
||||
})
|
||||
}
|
||||
|
||||
// mimeValues extracts the StringNode values from an OR group, dropping operators.
|
||||
func mimeValues(group *ast.GroupNode) []string {
|
||||
var out []string
|
||||
@@ -109,3 +21,91 @@ func mimeValues(group *ast.GroupNode) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var _ = Describe("Expand", func() {
|
||||
It("only triggers on the mediatype key", func() {
|
||||
Expect(mimetype.Expand("Name", "document")).To(BeNil())
|
||||
Expect(mimetype.Expand("MimeType", "file")).To(BeNil()) // the real field name is not the trigger
|
||||
Expect(mimetype.Expand("Tags", "file")).To(BeNil())
|
||||
})
|
||||
|
||||
It("matches the key case-insensitively", func() {
|
||||
Expect(mimetype.Expand("MediaType", "file")).ToNot(BeNil())
|
||||
})
|
||||
|
||||
It("matches the value case-insensitively", func() {
|
||||
// a category matches regardless of case
|
||||
Expect(mimetype.Expand("mediatype", "Folder")).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}))
|
||||
// a literal MIME type is lowercased too (MIME types are case-insensitive)
|
||||
Expect(mimetype.Expand("mediatype", "Image/SVG+XML")).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "image/svg+xml"},
|
||||
}))
|
||||
})
|
||||
|
||||
// A non-category value is a literal MIME type and targets the MimeType field.
|
||||
It("passes literal values through to MimeType", func() {
|
||||
Expect(mimetype.Expand("mediatype", "application/pdf")).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "application/pdf"},
|
||||
}))
|
||||
Expect(mimetype.Expand("mediatype", "image/jpeg")).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "image/jpeg"},
|
||||
}))
|
||||
})
|
||||
|
||||
It("expands file to not-a-folder", func() {
|
||||
// grouped so the negation stays atomic next to an operator.
|
||||
Expect(mimetype.Expand("mediatype", "file")).To(Equal([]ast.Node{
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.OperatorNode{Value: "NOT"},
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}},
|
||||
}))
|
||||
})
|
||||
|
||||
It("expands folder to a single term", func() {
|
||||
Expect(mimetype.Expand("mediatype", "folder")).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}))
|
||||
})
|
||||
|
||||
It("expands wildcard categories", func() {
|
||||
for value, mime := range map[string]string{
|
||||
"image": "image/*", "video": "video/*", "audio": "audio/*", "pdf": "application/pdf",
|
||||
} {
|
||||
Expect(mimetype.Expand("mediatype", value)).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "MimeType", Value: mime},
|
||||
}), value)
|
||||
}
|
||||
})
|
||||
|
||||
It("expands the document group", func() {
|
||||
got := mimetype.Expand("mediatype", "document")
|
||||
Expect(got).To(HaveLen(1))
|
||||
group, ok := got[0].(*ast.GroupNode)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(mimeValues(group)).To(Equal([]string{
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.form",
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"text/plain",
|
||||
"text/markdown",
|
||||
"application/rtf",
|
||||
"application/vnd.apple.pages",
|
||||
}))
|
||||
})
|
||||
|
||||
// spreadsheet asserts the exact MIME set, in order, with no duplicate entry.
|
||||
It("expands the spreadsheet group", func() {
|
||||
group := mimetype.Expand("mediatype", "spreadsheet")[0].(*ast.GroupNode)
|
||||
Expect(mimeValues(group)).To(Equal([]string{
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.oasis.opendocument.spreadsheet",
|
||||
"text/csv",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.apple.numbers",
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,8 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/ast"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/query"
|
||||
@@ -18,86 +17,93 @@ func norm(nodes ...ast.Node) []ast.Node {
|
||||
return query.Normalize(&ast.Ast{Nodes: nodes}, query.ResolveField).Nodes
|
||||
}
|
||||
|
||||
func TestResolveField(t *testing.T) {
|
||||
require.Equal(t, "Name", query.ResolveField("")) // empty -> free-text default
|
||||
require.Equal(t, "Name", query.ResolveField("NAME")) // canonical, case-insensitive key match
|
||||
require.Equal(t, "Tags", query.ResolveField("tag")) // singular alias
|
||||
require.Equal(t, "MimeType", query.ResolveField("mimetype")) // real field
|
||||
require.Equal(t, "photo.cameraMake", query.ResolveField("photo.CAMERAMAKE")) // facet, case-insensitive key match
|
||||
require.Equal(t, "unknown.field", query.ResolveField("unknown.field")) // unknown key: unchanged, becomes a dead query
|
||||
}
|
||||
var _ = Describe("ResolveField", func() {
|
||||
It("resolves keys to canonical field names", func() {
|
||||
Expect(query.ResolveField("")).To(Equal("Name")) // empty -> free-text default
|
||||
Expect(query.ResolveField("NAME")).To(Equal("Name")) // canonical, case-insensitive key match
|
||||
Expect(query.ResolveField("tag")).To(Equal("Tags")) // singular alias
|
||||
Expect(query.ResolveField("mimetype")).To(Equal("MimeType")) // real field
|
||||
Expect(query.ResolveField("photo.CAMERAMAKE")).To(Equal("photo.cameraMake")) // facet, case-insensitive key match
|
||||
Expect(query.ResolveField("unknown.field")).To(Equal("unknown.field")) // unknown key: unchanged, becomes a dead query
|
||||
})
|
||||
})
|
||||
|
||||
func TestFieldIsCaseInsensitive(t *testing.T) {
|
||||
// The four CaseInsensitive override fields (resolved canonical names).
|
||||
for _, f := range []string{"Name", "Path", "Tags", "Favorites"} {
|
||||
require.True(t, query.FieldIsCaseInsensitive(f), f)
|
||||
}
|
||||
// Case-preserved / non-keyword fields are not.
|
||||
for _, f := range []string{"MimeType", "ID", "Content", "unknown"} {
|
||||
require.False(t, query.FieldIsCaseInsensitive(f), f)
|
||||
}
|
||||
}
|
||||
var _ = Describe("FieldIsCaseInsensitive", func() {
|
||||
It("reports the CaseInsensitive override fields", func() {
|
||||
// The four CaseInsensitive override fields (resolved canonical names).
|
||||
for _, f := range []string{"Name", "Path", "Tags", "Favorites"} {
|
||||
Expect(query.FieldIsCaseInsensitive(f)).To(BeTrue(), f)
|
||||
}
|
||||
// Case-preserved / non-keyword fields are not.
|
||||
for _, f := range []string{"MimeType", "ID", "Content", "unknown"} {
|
||||
Expect(query.FieldIsCaseInsensitive(f)).To(BeFalse(), f)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
func TestNormalize_ResolvesFieldsAndExpandsMediatype(t *testing.T) {
|
||||
got := norm(
|
||||
&ast.StringNode{Key: "", Value: "free"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "TAG", Value: "x"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "photo.cameramake", Value: "canon"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "mediatype", Value: "file"},
|
||||
)
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "Name", Value: "free", CaseInsensitive: true},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "Tags", Value: "x", CaseInsensitive: true},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "photo.cameraMake", Value: "canon"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.OperatorNode{Value: "NOT"},
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}},
|
||||
}, got)
|
||||
}
|
||||
var _ = Describe("Normalize", func() {
|
||||
It("resolves fields and expands mediatype", func() {
|
||||
got := norm(
|
||||
&ast.StringNode{Key: "", Value: "free"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "TAG", Value: "x"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "photo.cameramake", Value: "canon"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "mediatype", Value: "file"},
|
||||
)
|
||||
Expect(got).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "Name", Value: "free", CaseInsensitive: true},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "Tags", Value: "x", CaseInsensitive: true},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.StringNode{Key: "photo.cameraMake", Value: "canon"},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.OperatorNode{Value: "NOT"},
|
||||
&ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"},
|
||||
}},
|
||||
}))
|
||||
})
|
||||
|
||||
// A bare restriction inside a named group inherits the group key; a keyed child
|
||||
// keeps its own key; a bare restriction in an unnamed group falls back to Name.
|
||||
func TestNormalize_GroupKeyDefaulting(t *testing.T) {
|
||||
got := norm(
|
||||
&ast.GroupNode{Key: "author", Nodes: []ast.Node{
|
||||
&ast.StringNode{Value: "b"},
|
||||
&ast.OperatorNode{Value: "OR"},
|
||||
&ast.StringNode{Key: "name", Value: "d"},
|
||||
}},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.StringNode{Value: "e"},
|
||||
}},
|
||||
)
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.GroupNode{Key: "author", Nodes: []ast.Node{
|
||||
&ast.StringNode{Key: "author", Value: "b"},
|
||||
&ast.OperatorNode{Value: "OR"},
|
||||
&ast.StringNode{Key: "Name", Value: "d", CaseInsensitive: true},
|
||||
}},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.StringNode{Key: "Name", Value: "e", CaseInsensitive: true},
|
||||
}},
|
||||
}, got)
|
||||
}
|
||||
// A bare restriction inside a named group inherits the group key; a keyed
|
||||
// child keeps its own key; a bare restriction in an unnamed group falls
|
||||
// back to Name.
|
||||
It("defaults group keys", func() {
|
||||
got := norm(
|
||||
&ast.GroupNode{Key: "author", Nodes: []ast.Node{
|
||||
&ast.StringNode{Value: "b"},
|
||||
&ast.OperatorNode{Value: "OR"},
|
||||
&ast.StringNode{Key: "name", Value: "d"},
|
||||
}},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.StringNode{Value: "e"},
|
||||
}},
|
||||
)
|
||||
Expect(got).To(Equal([]ast.Node{
|
||||
&ast.GroupNode{Key: "author", Nodes: []ast.Node{
|
||||
&ast.StringNode{Key: "author", Value: "b"},
|
||||
&ast.OperatorNode{Value: "OR"},
|
||||
&ast.StringNode{Key: "Name", Value: "d", CaseInsensitive: true},
|
||||
}},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.GroupNode{Nodes: []ast.Node{
|
||||
&ast.StringNode{Key: "Name", Value: "e", CaseInsensitive: true},
|
||||
}},
|
||||
}))
|
||||
})
|
||||
|
||||
func TestNormalize_ConvertsValueNodesToPointers(t *testing.T) {
|
||||
got := norm(
|
||||
ast.StringNode{Key: "name", Value: "x"},
|
||||
ast.OperatorNode{Value: "AND"},
|
||||
ast.DateTimeNode{Key: "mtime"},
|
||||
)
|
||||
require.Equal(t, []ast.Node{
|
||||
&ast.StringNode{Key: "Name", Value: "x", CaseInsensitive: true},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.DateTimeNode{Key: "Mtime"},
|
||||
}, got)
|
||||
}
|
||||
It("converts value nodes to pointers", func() {
|
||||
got := norm(
|
||||
ast.StringNode{Key: "name", Value: "x"},
|
||||
ast.OperatorNode{Value: "AND"},
|
||||
ast.DateTimeNode{Key: "mtime"},
|
||||
)
|
||||
Expect(got).To(Equal([]ast.Node{
|
||||
&ast.StringNode{Key: "Name", Value: "x", CaseInsensitive: true},
|
||||
&ast.OperatorNode{Value: "AND"},
|
||||
&ast.DateTimeNode{Key: "Mtime"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Query Suite")
|
||||
}
|
||||
Reference in new issue
Block a user