Files
temporal/common/searchattribute/stringify_test.go
Stephan Behnke 3bc2ffb276 Apply lint fixes for oss-foundations (#10796)
## What changed?

- Applied testifylint, staticcheck, and gofix auto-fixes.
- Exact commands that were run:

```sh
.bin/golangci-lint-v2.9.0 run --allow-parallel-runners --concurrency 4 --fix --enable-only testifylint --build-tags disable_grpc_modules,test_dep --timeout 20m --config=.github/.golangci.yml
.bin/golangci-lint-v2.9.0 run --allow-parallel-runners --concurrency 4 --fix --enable-only staticcheck --build-tags disable_grpc_modules,test_dep --timeout 20m --config=.github/.golangci.yml
make fmt-gofix
make goimports
make fmt
git diff --check
```

- No manual or AI changes were made; except where commented on.
- Some fixes caused lint errors; those were reverted again.
- Changes were all reviewed by me.
2026-06-24 11:04:58 -07:00

412 lines
11 KiB
Go

package searchattribute
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/server/common/searchattribute/sadefs"
)
type StringifySuite struct {
suite.Suite
*require.Assertions
}
func TestStringifySuite(t *testing.T) {
s := &StringifySuite{
Assertions: require.New(t),
}
suite.Run(t, s)
}
func (s *StringifySuite) SetupSuite() {
}
func (s *StringifySuite) SetupTest() {
}
func (s *StringifySuite) TearDownTest() {
}
func (s *StringifySuite) Test_Stringify() {
typeMap := NameTypeMap{
customSearchAttributes: map[string]enumspb.IndexedValueType{
"key1": enumspb.INDEXED_VALUE_TYPE_TEXT,
"key2": enumspb.INDEXED_VALUE_TYPE_INT,
"key3": enumspb.INDEXED_VALUE_TYPE_BOOL,
},
}
sa, err := Encode(map[string]any{
"key1": "val1",
"key2": 2,
"key3": true,
}, &typeMap)
s.NoError(err)
saStr, err := Stringify(sa, nil)
s.NoError(err)
s.Len(saStr, 3)
s.Equal("val1", saStr["key1"])
s.Equal("2", saStr["key2"])
s.Equal("true", saStr["key3"])
// Clean Metadata type and use typeMap.
delete(sa.IndexedFields["key1"].Metadata, "type")
delete(sa.IndexedFields["key2"].Metadata, "type")
delete(sa.IndexedFields["key3"].Metadata, "type")
saStr, err = Stringify(sa, &typeMap)
s.NoError(err)
s.Len(saStr, 3)
s.Equal("val1", saStr["key1"])
s.Equal("2", saStr["key2"])
s.Equal("true", saStr["key3"])
// Even w/o typeMap error is returned but string values are set with raw JSON from GetData().
saStr, err = Stringify(sa, nil)
s.Error(err)
s.ErrorIs(err, sadefs.ErrInvalidType)
s.Len(saStr, 3)
s.Equal(`"val1"`, saStr["key1"])
s.Equal("2", saStr["key2"])
s.Equal("true", saStr["key3"])
}
func (s *StringifySuite) Test_Stringify_Array() {
typeMap := NameTypeMap{
customSearchAttributes: map[string]enumspb.IndexedValueType{
"key1": enumspb.INDEXED_VALUE_TYPE_TEXT,
"key2": enumspb.INDEXED_VALUE_TYPE_INT,
"key3": enumspb.INDEXED_VALUE_TYPE_BOOL,
},
}
sa, err := Encode(map[string]any{
"key1": []string{"val1", "val2"},
"key2": []int64{2, 3, 4},
"key3": []bool{true, false, true},
}, &typeMap)
s.NoError(err)
saStr, err := Stringify(sa, nil)
s.NoError(err)
s.Len(saStr, 3)
s.Equal(`["val1","val2"]`, saStr["key1"])
s.Equal("[2,3,4]", saStr["key2"])
s.Equal("[true,false,true]", saStr["key3"])
// Clean Metadata type and use typeMap.
delete(sa.IndexedFields["key1"].Metadata, "type")
delete(sa.IndexedFields["key2"].Metadata, "type")
delete(sa.IndexedFields["key3"].Metadata, "type")
saStr, err = Stringify(sa, &typeMap)
s.NoError(err)
s.Len(saStr, 3)
s.Equal(`["val1","val2"]`, saStr["key1"])
s.Equal("[2,3,4]", saStr["key2"])
s.Equal("[true,false,true]", saStr["key3"])
// Even w/o typeMap error is returned but string values are set with raw JSON from GetData().
saStr, err = Stringify(sa, nil)
s.Error(err)
s.ErrorIs(err, sadefs.ErrInvalidType)
s.Len(saStr, 3)
s.Equal(`["val1","val2"]`, saStr["key1"])
s.Equal("[2,3,4]", saStr["key2"])
s.Equal("[true,false,true]", saStr["key3"])
}
func (s *StringifySuite) Test_Parse_ValidTypeMap() {
sa, err := Parse(map[string]string{
"key1": "val1",
"key2": "2",
"key3": "true",
}, &NameTypeMap{
customSearchAttributes: map[string]enumspb.IndexedValueType{
"key1": enumspb.INDEXED_VALUE_TYPE_TEXT,
"key2": enumspb.INDEXED_VALUE_TYPE_INT,
"key3": enumspb.INDEXED_VALUE_TYPE_BOOL,
}})
s.NoError(err)
s.Len(sa.IndexedFields, 3)
s.Equal(`"val1"`, string(sa.IndexedFields["key1"].GetData()))
s.Equal("Text", string(sa.IndexedFields["key1"].GetMetadata()["type"]))
s.Equal("2", string(sa.IndexedFields["key2"].GetData()))
s.Equal("Int", string(sa.IndexedFields["key2"].GetMetadata()["type"]))
s.Equal("true", string(sa.IndexedFields["key3"].GetData()))
s.Equal("Bool", string(sa.IndexedFields["key3"].GetMetadata()["type"]))
}
func (s *StringifySuite) Test_Parse_NilTypeMap() {
sa, err := Parse(map[string]string{
"key1": "val1",
"key2": "2",
"key3": "true",
}, nil)
s.NoError(err)
s.Len(sa.IndexedFields, 3)
s.Equal(`"val1"`, string(sa.IndexedFields["key1"].GetData()))
s.Equal("2", string(sa.IndexedFields["key2"].GetData()))
s.Equal("true", string(sa.IndexedFields["key3"].GetData()))
}
func (s *StringifySuite) Test_Parse_WrongTypesInTypeMap() {
sa, err := Parse(map[string]string{
"key1": "val1",
"key2": "2",
}, &NameTypeMap{
customSearchAttributes: map[string]enumspb.IndexedValueType{
"key1": enumspb.INDEXED_VALUE_TYPE_INT,
"key2": enumspb.INDEXED_VALUE_TYPE_TEXT,
}})
s.Error(err)
s.Len(sa.IndexedFields, 2)
s.Nil(sa.IndexedFields["key1"])
s.Equal(`"2"`, string(sa.IndexedFields["key2"].GetData()))
s.Equal("Text", string(sa.IndexedFields["key2"].GetMetadata()["type"]))
}
func (s *StringifySuite) Test_Parse_MissedFieldsInTypeMap() {
sa, err := Parse(map[string]string{
"key1": "val1",
"key2": "2",
}, &NameTypeMap{
customSearchAttributes: map[string]enumspb.IndexedValueType{
"key3": enumspb.INDEXED_VALUE_TYPE_TEXT,
"key2": enumspb.INDEXED_VALUE_TYPE_INT,
}})
s.NoError(err)
s.Len(sa.IndexedFields, 2)
s.Equal(`"val1"`, string(sa.IndexedFields["key1"].GetData()))
s.Nil(sa.IndexedFields["key1"].GetMetadata()["type"])
s.Equal("2", string(sa.IndexedFields["key2"].GetData()))
s.Equal("Int", string(sa.IndexedFields["key2"].GetMetadata()["type"]))
}
func (s *StringifySuite) Test_Parse_Array() {
sa, err := Parse(map[string]string{
"key1": ` ["val1", "val2"] `,
"key2": "[2,3,4]",
}, &NameTypeMap{
customSearchAttributes: map[string]enumspb.IndexedValueType{
"key1": enumspb.INDEXED_VALUE_TYPE_TEXT,
"key2": enumspb.INDEXED_VALUE_TYPE_INT,
}})
s.NoError(err)
s.Len(sa.IndexedFields, 2)
s.Equal(`["val1","val2"]`, string(sa.IndexedFields["key1"].GetData()))
s.Equal("Text", string(sa.IndexedFields["key1"].GetMetadata()["type"]))
s.Equal("[2,3,4]", string(sa.IndexedFields["key2"].GetData()))
s.Equal("Int", string(sa.IndexedFields["key2"].GetMetadata()["type"]))
}
func (s *StringifySuite) Test_parseValueOrArray() {
var res *commonpb.Payload
var err error
// int
res, err = parseValueOrArray("1", enumspb.INDEXED_VALUE_TYPE_INT)
s.NoError(err)
s.Equal("Int", string(res.Metadata["type"]))
s.Equal("1", string(res.Data))
// array must be in JSON format.
res, err = parseValueOrArray(`["qwe"]`, enumspb.INDEXED_VALUE_TYPE_TEXT)
s.NoError(err)
s.Equal("Text", string(res.Metadata["type"]))
s.Equal(`["qwe"]`, string(res.Data))
// array must be in JSON format.
res, err = parseValueOrArray(`[qwe]`, enumspb.INDEXED_VALUE_TYPE_TEXT)
s.Error(err)
s.Nil(res)
}
func (s *StringifySuite) Test_parseValueTyped() {
var res any
var err error
// int
res, err = parseValueTyped("1", enumspb.INDEXED_VALUE_TYPE_INT)
s.NoError(err)
s.Equal(int64(1), res)
res, err = parseValueTyped("qwe", enumspb.INDEXED_VALUE_TYPE_INT)
s.Error(err)
s.Equal(int64(0), res)
// bool
res, err = parseValueTyped("true", enumspb.INDEXED_VALUE_TYPE_BOOL)
s.NoError(err)
s.Equal(true, res)
res, err = parseValueTyped("false", enumspb.INDEXED_VALUE_TYPE_BOOL)
s.NoError(err)
s.Equal(false, res)
res, err = parseValueTyped("qwe", enumspb.INDEXED_VALUE_TYPE_BOOL)
s.Error(err)
s.Equal(false, res)
// double
res, err = parseValueTyped("1.0", enumspb.INDEXED_VALUE_TYPE_DOUBLE)
s.NoError(err)
s.Equal(float64(1.0), res)
res, err = parseValueTyped("qwe", enumspb.INDEXED_VALUE_TYPE_DOUBLE)
s.Error(err)
s.Equal(float64(0), res)
// datetime
res, err = parseValueTyped("2019-01-01T01:01:01Z", enumspb.INDEXED_VALUE_TYPE_DATETIME)
s.NoError(err)
s.Equal(time.Date(2019, 1, 1, 1, 1, 1, 0, time.UTC), res)
res, err = parseValueTyped("qwe", enumspb.INDEXED_VALUE_TYPE_DATETIME)
s.Error(err)
s.Equal(time.Time{}, res)
// string
res, err = parseValueTyped("test string", enumspb.INDEXED_VALUE_TYPE_TEXT)
s.NoError(err)
s.Equal("test string", res)
// unspecified
res, err = parseValueTyped("test string", enumspb.INDEXED_VALUE_TYPE_UNSPECIFIED)
s.NoError(err)
s.Equal("test string", res)
}
func (s *StringifySuite) Test_parseValueUnspecified() {
var res any
// int
res = parseValueUnspecified("1")
s.Equal(int64(1), res)
// bool
res = parseValueUnspecified("true")
s.Equal(true, res)
res = parseValueUnspecified("false")
s.Equal(false, res)
// double
res = parseValueUnspecified("1.0")
s.Equal(float64(1.0), res)
// datetime
res = parseValueUnspecified("2019-01-01T01:01:01Z")
s.Equal(time.Date(2019, 1, 1, 1, 1, 1, 0, time.UTC), res)
// array
res = parseValueUnspecified(`["a", "b", "c"]`)
s.Equal([]any{"a", "b", "c"}, res)
// string
res = parseValueUnspecified("test string")
s.Equal("test string", res)
}
func (s *StringifySuite) Test_isJSONArray() {
s.True(isJSONArray("[1,2,3]"))
s.True(isJSONArray(" [1,2,3] "))
s.True(isJSONArray(` ["1","2","3"] `))
s.True(isJSONArray("[]"))
s.False(isJSONArray("["))
s.False(isJSONArray("]"))
s.False(isJSONArray("qwe"))
s.False(isJSONArray("123"))
}
func (s *StringifySuite) Test_parseJSONArray() {
t1, _ := time.Parse(time.RFC3339Nano, "2019-06-07T16:16:34-08:00")
t2, _ := time.Parse(time.RFC3339Nano, "2019-06-07T17:16:34-08:00")
testCases := []struct {
name string
indexedValueType enumspb.IndexedValueType
input string
expected any
}{
{
name: "string",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_TEXT,
input: `["a", "b", "c"]`,
expected: []string{"a", "b", "c"},
},
{
name: "int",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_INT,
input: `[1, 2, 3]`,
expected: []int64{1, 2, 3},
},
{
name: "double",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_DOUBLE,
input: `[1.1, 2.2, 3.3]`,
expected: []float64{1.1, 2.2, 3.3},
},
{
name: "bool",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_BOOL,
input: `[true, false]`,
expected: []bool{true, false},
},
{
name: "datetime",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_DATETIME,
input: `["2019-06-07T16:16:34-08:00", "2019-06-07T17:16:34-08:00"]`,
expected: []time.Time{t1, t2},
},
{
name: "unspecified",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_UNSPECIFIED,
input: `["a", "b", "c"]`,
expected: []any{"a", "b", "c"},
},
}
for _, testCase := range testCases {
s.Run(testCase.name, func() {
res, err := parseJSONArray(testCase.input, testCase.indexedValueType)
s.NoError(err)
s.Equal(testCase.expected, res)
})
}
testCases2 := []struct {
name string
indexedValueType enumspb.IndexedValueType
input string
expected error
}{
{
name: "not array",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_TEXT,
input: "normal string",
},
{
name: "empty string",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_TEXT,
input: "",
},
{
name: "not json array",
indexedValueType: enumspb.INDEXED_VALUE_TYPE_TEXT,
input: "[a, b, c]",
},
}
for _, testCase := range testCases2 {
res, err := parseJSONArray(testCase.input, testCase.indexedValueType)
s.Error(err)
s.Nil(res)
}
}