mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Follow up to #7761: 1. Use more concrete types instead of `comparable`. 2. Use `softassert` for "compile" time errors. 3. Rename `chasm.Collection` to `chasm.Map` but left proto `CollectionAttributes` intact. This will allow to add support for other collection type in future (slice, array). ## Why? It is better to narrow key type as much as possible. Other types are not supported anyway. ## How did you test it? - [ ] built - [ ] run locally and tested manually - [x] covered by existing tests - [x] added new unit test(s) - [ ] added new functional test(s)
198 lines
5.5 KiB
Go
198 lines
5.5 KiB
Go
package chasm
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"go.uber.org/mock/gomock"
|
|
)
|
|
|
|
type fieldsIteratorSuite struct {
|
|
suite.Suite
|
|
*require.Assertions
|
|
|
|
controller *gomock.Controller
|
|
}
|
|
|
|
func TestFieldsIteratorSuite(t *testing.T) {
|
|
suite.Run(t, new(fieldsIteratorSuite))
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) SetupTest() {
|
|
s.initAssertions()
|
|
s.controller = gomock.NewController(s.T())
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) SetupSubTest() {
|
|
s.initAssertions()
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) initAssertions() {
|
|
// `s.Assertions` (as well as other test helpers which depends on `s.T()`) must be initialized on
|
|
// both test and subtest levels (but not suite level, where `s.T()` is `nil`).
|
|
//
|
|
// If these helpers are not reinitialized on subtest level, any failed `assert` in
|
|
// subtest will fail the entire test (not subtest) immediately without running other subtests.
|
|
|
|
s.Assertions = require.New(s.T())
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) TestGenericTypePrefix() {
|
|
tests := []struct {
|
|
name string
|
|
input any
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Field type",
|
|
input: Field[string]{},
|
|
expected: chasmFieldTypePrefix,
|
|
},
|
|
{
|
|
name: "Map type",
|
|
input: Map[string, int]{},
|
|
expected: chasmMapTypePrefix,
|
|
},
|
|
{
|
|
name: "Non-generic type",
|
|
input: 0,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Map type",
|
|
input: map[string]int{},
|
|
expected: "map[",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
s.Run(tt.name, func() {
|
|
typ := reflect.TypeOf(tt.input)
|
|
result := genericTypePrefix(typ)
|
|
s.Equal(tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) TestChasmFieldTypePrefix() {
|
|
f := Field[any]{}
|
|
fT := reflect.TypeOf(f)
|
|
s.True(strings.HasPrefix(fT.String(), chasmFieldTypePrefix))
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) TestChasmMapTypePrefix() {
|
|
c := Map[string, any]{}
|
|
cT := reflect.TypeOf(c)
|
|
s.True(strings.HasPrefix(cT.String(), chasmMapTypePrefix))
|
|
}
|
|
|
|
func (s *fieldsIteratorSuite) TestFieldsOf() {
|
|
type fieldPointer struct {
|
|
DataField *protoMessageType
|
|
InvalidField *Field[string]
|
|
}
|
|
|
|
type noDataField struct {
|
|
SubField Field[string]
|
|
SubMap Map[string, int]
|
|
}
|
|
|
|
type twoDataFields struct {
|
|
DataField *protoMessageType
|
|
AnotherDataField *protoMessageType
|
|
}
|
|
|
|
type unimplementedComponentOnly struct {
|
|
UnimplementedComponent
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
input any
|
|
expectedKinds []fieldKind
|
|
expectedNames []string
|
|
expectedTypes []string
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
name: "Valid component with one data field",
|
|
input: &struct {
|
|
UnimplementedComponent
|
|
DataField *protoMessageType
|
|
SubField Field[string]
|
|
SubMap Map[string, int]
|
|
}{},
|
|
expectedKinds: []fieldKind{fieldKindData, fieldKindSubField, fieldKindSubMap},
|
|
expectedNames: []string{"DataField", "SubField", "SubMap"},
|
|
expectedTypes: []string{"*persistence.WorkflowExecutionState", "chasm.Field[string]", "chasm.Map[string,int]"},
|
|
expectedErrors: []string{"", "", ""},
|
|
},
|
|
{
|
|
name: "Component with no data field",
|
|
input: &noDataField{},
|
|
expectedKinds: []fieldKind{fieldKindSubField, fieldKindSubMap, fieldKindUnspecified},
|
|
expectedNames: []string{"SubField", "SubMap", ""},
|
|
expectedTypes: []string{"chasm.Field[string]", "chasm.Map[string,int]", ""},
|
|
expectedErrors: []string{"", "", "*chasm.noDataField: no data field (implements proto.Message) found"},
|
|
},
|
|
{
|
|
name: "Component with *Field",
|
|
input: &fieldPointer{},
|
|
expectedKinds: []fieldKind{fieldKindData, fieldKindUnspecified},
|
|
expectedNames: []string{"DataField", "InvalidField"},
|
|
expectedTypes: []string{"*persistence.WorkflowExecutionState", "*chasm.Field[string]"},
|
|
expectedErrors: []string{"", "*chasm.fieldPointer.InvalidField: chasm field type *chasm.Field[string] must not be a pointer"},
|
|
},
|
|
{
|
|
name: "Component with multiple data fields",
|
|
input: &twoDataFields{},
|
|
expectedKinds: []fieldKind{fieldKindData, fieldKindData},
|
|
expectedNames: []string{"DataField", "AnotherDataField"},
|
|
expectedTypes: []string{"*persistence.WorkflowExecutionState", "*persistence.WorkflowExecutionState"},
|
|
expectedErrors: []string{"", "*chasm.twoDataFields.AnotherDataField: only one data field DataField (implements proto.Message) allowed in component"},
|
|
},
|
|
{
|
|
name: "Component with UnimplementedComponent only",
|
|
input: &unimplementedComponentOnly{},
|
|
expectedKinds: []fieldKind{fieldKindUnspecified},
|
|
expectedNames: []string{""},
|
|
expectedTypes: []string{""},
|
|
expectedErrors: []string{"*chasm.unimplementedComponentOnly: no data field (implements proto.Message) found"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
s.Run(tt.name, func() {
|
|
valueV := reflect.ValueOf(tt.input)
|
|
|
|
var actualKinds []fieldKind
|
|
var actualNames []string
|
|
var actualTypes []string
|
|
var actualErrors []string
|
|
|
|
for field := range fieldsOf(valueV) {
|
|
actualKinds = append(actualKinds, field.kind)
|
|
actualNames = append(actualNames, field.name)
|
|
if field.typ != nil {
|
|
actualTypes = append(actualTypes, field.typ.String())
|
|
} else {
|
|
actualTypes = append(actualTypes, "")
|
|
}
|
|
if field.err != nil {
|
|
actualErrors = append(actualErrors, field.err.Error())
|
|
} else {
|
|
actualErrors = append(actualErrors, "")
|
|
}
|
|
}
|
|
|
|
s.Equal(tt.expectedKinds, actualKinds)
|
|
s.Equal(tt.expectedNames, actualNames)
|
|
s.Equal(tt.expectedTypes, actualTypes)
|
|
s.Equal(tt.expectedErrors, actualErrors)
|
|
})
|
|
}
|
|
}
|