package model import ( "fmt" "testing" "time" "github.com/tech/sendico/pkg/mservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) func createTestPermissionBound() PermissionBound { pb := PermissionBound{ PermissionRef: primitive.NewObjectID(), } pb.OrganizationRef = primitive.NewObjectID() return pb } func createTestRecordRef() ObjectRef { return ObjectRef{ Ref: primitive.NewObjectID(), } } func createTestPropertyScheme(propertyType PropertyType, key string) PropertySchema { desc := "Test property scheme" ps := PropertySchema{ PermissionBound: createTestPermissionBound(), Describable: Describable{ Name: key, Description: &desc, }, Key: key, Type: propertyType, Multiplicity: Multiplicity{ Mode: One, // Default to single values }, } ps.ID = primitive.NewObjectID() // Set appropriate Props based on type switch propertyType { case PTString: ps.Props = StringProps{} case PTColor: ps.Props = ColorProps{} case PTInteger: ps.Props = IntegerProps{} case PTFloat: ps.Props = FloatProps{} case PTDateTime: ps.Props = DateTimeProps{} case PTMonetary: ps.Props = MonetaryProps{} case PTReference: ps.Props = ReferenceProps{} case PTObject: ps.Props = ObjectProps{} } return ps } func createTestPropertySchemeMany(propertyType PropertyType, key string) PropertySchema { desc := "Test property scheme" ps := PropertySchema{ PermissionBound: createTestPermissionBound(), Describable: Describable{ Name: key, Description: &desc, }, Key: key, Type: propertyType, Multiplicity: Multiplicity{ Mode: Many, // Allow multiple values }, } ps.ID = primitive.NewObjectID() // Set appropriate Props based on type switch propertyType { case PTString: ps.Props = StringProps{} case PTColor: ps.Props = ColorProps{} case PTInteger: ps.Props = IntegerProps{} case PTFloat: ps.Props = FloatProps{} case PTDateTime: ps.Props = DateTimeProps{} case PTMonetary: ps.Props = MonetaryProps{} case PTReference: ps.Props = ReferenceProps{} case PTObject: ps.Props = ObjectProps{} } return ps } func TestValue(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTString, "test_string") value := Value{ PermissionBound: scope, Target: target, Type: PTString, Cardinality: One, PropertySchemaRef: scheme.ID, Values: SettingsT{"string": "test_value"}, } assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTString, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) } func TestAsString(t *testing.T) { t.Run("valid string value", func(t *testing.T) { value := Value{ Type: PTString, Cardinality: One, Values: SettingsT{"string": "hello"}, } result, err := value.AsString() require.NoError(t, err) assert.Equal(t, "hello", result) }) t.Run("invalid type", func(t *testing.T) { value := Value{ Type: PTInteger, Cardinality: One, Values: SettingsT{"integer": 42}, } _, err := value.AsString() assert.Error(t, err) }) t.Run("invalid cardinality", func(t *testing.T) { value := Value{ Type: PTString, Cardinality: Many, Values: SettingsT{"strings": []string{"hello"}}, } _, err := value.AsString() assert.Error(t, err) }) } func TestAsColor(t *testing.T) { t.Run("valid color value", func(t *testing.T) { value := Value{ Type: PTColor, Cardinality: One, Values: SettingsT{"color": "#FF0000"}, } result, err := value.AsColor() require.NoError(t, err) assert.Equal(t, "#FF0000", result) }) } func TestAsInteger(t *testing.T) { t.Run("valid integer value", func(t *testing.T) { value := Value{ Type: PTInteger, Cardinality: One, Values: SettingsT{"integer": int64(42)}, } result, err := value.AsInteger() require.NoError(t, err) assert.Equal(t, int64(42), result) }) } func TestAsFloat(t *testing.T) { t.Run("valid float value", func(t *testing.T) { value := Value{ Type: PTFloat, Cardinality: One, Values: SettingsT{"float": 3.14}, } result, err := value.AsFloat() require.NoError(t, err) assert.Equal(t, 3.14, result) }) } func TestAsDateTime(t *testing.T) { t.Run("valid datetime value", func(t *testing.T) { now := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) value := Value{ Type: PTDateTime, Cardinality: One, Values: SettingsT{"date_time": now}, } result, err := value.AsDateTime() require.NoError(t, err) assert.Equal(t, now, result) }) } func TestAsMonetary(t *testing.T) { t.Run("valid monetary value", func(t *testing.T) { money := Money{ Amount: primitive.NewDecimal128(10000, 0), // $100.00 Currency: CurrencyUSD, } value := Value{ Type: PTMonetary, Cardinality: One, Values: SettingsT{"monetary": money}, } result, err := value.AsMonetary() require.NoError(t, err) assert.Equal(t, money, result) }) } func TestAsReference(t *testing.T) { t.Run("valid reference value", func(t *testing.T) { ref := primitive.NewObjectID() value := Value{ Type: PTReference, Cardinality: One, Values: SettingsT{"reference": ref}, } result, err := value.AsReference() require.NoError(t, err) assert.Equal(t, ref, result) }) } func TestAsObject(t *testing.T) { t.Run("valid object value", func(t *testing.T) { obj := Object{ "field1": Value{ Type: PTString, Cardinality: One, Values: SettingsT{"string": "value1"}, }, } value := Value{ Type: PTObject, Cardinality: One, Values: SettingsT{"object": obj}, } result, err := value.AsObject() require.NoError(t, err) assert.Equal(t, obj, result) }) } func TestAsStrings(t *testing.T) { t.Run("valid strings value", func(t *testing.T) { value := Value{ Type: PTString, Cardinality: Many, Values: SettingsT{"strings": []string{"hello", "world"}}, } result, err := value.AsStrings() require.NoError(t, err) assert.Equal(t, []string{"hello", "world"}, result) }) } func TestAsColors(t *testing.T) { t.Run("valid colors value", func(t *testing.T) { value := Value{ Type: PTColor, Cardinality: Many, Values: SettingsT{"colors": []string{"#FF0000", "#00FF00"}}, } result, err := value.AsColors() require.NoError(t, err) assert.Equal(t, []string{"#FF0000", "#00FF00"}, result) }) } func TestAsIntegers(t *testing.T) { t.Run("valid integers value", func(t *testing.T) { value := Value{ Type: PTInteger, Cardinality: Many, Values: SettingsT{"integers": []int64{1, 2, 3}}, } result, err := value.AsIntegers() require.NoError(t, err) assert.Equal(t, []int64{1, 2, 3}, result) }) } func TestAsFloats(t *testing.T) { t.Run("valid floats value", func(t *testing.T) { value := Value{ Type: PTFloat, Cardinality: Many, Values: SettingsT{"floats": []float64{1.1, 2.2, 3.3}}, } result, err := value.AsFloats() require.NoError(t, err) assert.Equal(t, []float64{1.1, 2.2, 3.3}, result) }) } func TestAsDateTimes(t *testing.T) { t.Run("valid datetimes value", func(t *testing.T) { now1 := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) now2 := time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC) value := Value{ Type: PTDateTime, Cardinality: Many, Values: SettingsT{"date_times": []time.Time{now1, now2}}, } result, err := value.AsDateTimes() require.NoError(t, err) assert.Equal(t, []time.Time{now1, now2}, result) }) } func TestAsMonetaries(t *testing.T) { t.Run("valid monetaries value", func(t *testing.T) { money1 := Money{Amount: primitive.NewDecimal128(10000, 0), Currency: CurrencyUSD} money2 := Money{Amount: primitive.NewDecimal128(20000, 0), Currency: CurrencyUSD} value := Value{ Type: PTMonetary, Cardinality: Many, Values: SettingsT{"monetaries": []Money{money1, money2}}, } result, err := value.AsMonetaries() require.NoError(t, err) assert.Equal(t, []Money{money1, money2}, result) }) } func TestAsReferences(t *testing.T) { t.Run("valid references value", func(t *testing.T) { ref1 := primitive.NewObjectID() ref2 := primitive.NewObjectID() value := Value{ Type: PTReference, Cardinality: Many, Values: SettingsT{"references": []primitive.ObjectID{ref1, ref2}}, } result, err := value.AsReferences() require.NoError(t, err) assert.Equal(t, []primitive.ObjectID{ref1, ref2}, result) }) } func TestAsObjects(t *testing.T) { t.Run("valid objects value", func(t *testing.T) { obj1 := Object{"field1": Value{Type: PTString, Cardinality: One, Values: SettingsT{"string": "value1"}}} obj2 := Object{"field2": Value{Type: PTString, Cardinality: One, Values: SettingsT{"string": "value2"}}} value := Value{ Type: PTObject, Cardinality: Many, Values: SettingsT{"objects": []Object{obj1, obj2}}, } result, err := value.AsObjects() require.NoError(t, err) assert.Equal(t, []Object{obj1, obj2}, result) }) } func TestNewStringValue(t *testing.T) { t.Run("valid string value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTString, "test_string") value, err := NewStringValue(scope, target, scheme, "hello") require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTString, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"string": "hello"}, value.Values) }) t.Run("invalid type", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTInteger, "test_int") _, err := NewStringValue(scope, target, scheme, "hello") assert.Error(t, err) }) } func TestNewStringsValue(t *testing.T) { t.Run("valid strings value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTString, "test_strings") value, err := NewStringsValue(scope, target, scheme, []string{"hello", "world"}) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTString, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"strings": []string{"hello", "world"}}, value.Values) }) } func TestNewColorValue(t *testing.T) { t.Run("valid color value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTColor, "test_color") value, err := NewColorValue(scope, target, scheme, "#FF0000") require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTColor, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"color": "#FF0000"}, value.Values) }) } func TestNewColorsValue(t *testing.T) { t.Run("valid colors value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTColor, "test_colors") value, err := NewColorsValue(scope, target, scheme, []string{"#FF0000", "#00FF00"}) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTColor, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"colors": []string{"#FF0000", "#00FF00"}}, value.Values) }) } func TestNewIntegerValue(t *testing.T) { t.Run("valid integer value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTInteger, "test_int") value, err := NewIntegerValue(scope, target, scheme, 42) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTInteger, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"integer": int64(42)}, value.Values) }) } func TestNewIntegersValue(t *testing.T) { t.Run("valid integers value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTInteger, "test_ints") value, err := NewIntegersValue(scope, target, scheme, []int64{1, 2, 3}) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTInteger, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"integers": []int64{1, 2, 3}}, value.Values) }) } func TestNewFloatValue(t *testing.T) { t.Run("valid float value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTFloat, "test_float") value, err := NewFloatValue(scope, target, scheme, 3.14) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTFloat, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"float": 3.14}, value.Values) }) } func TestNewFloatsValue(t *testing.T) { t.Run("valid floats value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTFloat, "test_floats") value, err := NewFloatsValue(scope, target, scheme, []float64{1.1, 2.2, 3.3}) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTFloat, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"floats": []float64{1.1, 2.2, 3.3}}, value.Values) }) } func TestNewDateTimeValue(t *testing.T) { t.Run("valid datetime value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTDateTime, "test_datetime") now := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) value, err := NewDateTimeValue(scope, target, scheme, now) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTDateTime, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"date_time": now}, value.Values) }) } func TestNewDateTimesValue(t *testing.T) { t.Run("valid datetimes value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTDateTime, "test_datetimes") now1 := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) now2 := time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC) value, err := NewDateTimesValue(scope, target, scheme, []time.Time{now1, now2}) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTDateTime, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"date_times": []time.Time{now1, now2}}, value.Values) }) } func TestNewMonetaryValue(t *testing.T) { t.Run("valid monetary value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTMonetary, "test_monetary") money := Money{ Amount: primitive.NewDecimal128(10000, 0), // $100.00 Currency: CurrencyUSD, } value, err := NewMonetaryValue(scope, target, scheme, money, CurrencyUSD) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTMonetary, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"monetary": money}, value.Values) }) } func TestNewMonetariesValue(t *testing.T) { t.Run("valid monetaries value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTMonetary, "test_monetaries") money1 := Money{Amount: primitive.NewDecimal128(10000, 0), Currency: CurrencyUSD} money2 := Money{Amount: primitive.NewDecimal128(20000, 0), Currency: CurrencyUSD} value, err := NewMonetariesValue(scope, target, scheme, []Money{money1, money2}, CurrencyUSD) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTMonetary, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"monetaries": []Money{money1, money2}}, value.Values) }) } func TestNewReferenceValue(t *testing.T) { t.Run("valid reference value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTReference, "test_reference") ref := primitive.NewObjectID() value, err := NewReferenceValue(scope, target, scheme, ref, func(resource mservice.Type, id primitive.ObjectID, filter bson.M) (bool, error) { return true, nil }) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTReference, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"reference": ref}, value.Values) }) } func TestNewReferencesValue(t *testing.T) { t.Run("valid references value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTReference, "test_references") ref1 := primitive.NewObjectID() ref2 := primitive.NewObjectID() value, err := NewReferencesValue(scope, target, scheme, []primitive.ObjectID{ref1, ref2}, func(resource mservice.Type, id primitive.ObjectID, filter bson.M) (bool, error) { return true, nil }) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTReference, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"references": []primitive.ObjectID{ref1, ref2}}, value.Values) }) } func TestNewObjectValue(t *testing.T) { t.Run("valid object value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertyScheme(PTObject, "test_object") obj := Object{ "field1": Value{ Type: PTString, Cardinality: One, Values: SettingsT{"string": "value1"}, }, } value, err := NewObjectValue(scope, target, scheme, obj) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTObject, value.Type) assert.Equal(t, One, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"object": obj}, value.Values) }) } func TestNewObjectsValue(t *testing.T) { t.Run("valid objects value", func(t *testing.T) { scope := createTestPermissionBound() target := createTestRecordRef() scheme := createTestPropertySchemeMany(PTObject, "test_objects") obj1 := Object{"field1": Value{Type: PTString, Cardinality: One, Values: SettingsT{"string": "value1"}}} obj2 := Object{"field2": Value{Type: PTString, Cardinality: One, Values: SettingsT{"string": "value2"}}} value, err := NewObjectsValue(scope, target, scheme, []Object{obj1, obj2}) require.NoError(t, err) assert.Equal(t, scope, value.PermissionBound) assert.Equal(t, target, value.Target) assert.Equal(t, PTObject, value.Type) assert.Equal(t, Many, value.Cardinality) assert.Equal(t, scheme.ID, value.PropertySchemaRef) assert.Equal(t, SettingsT{"objects": []Object{obj1, obj2}}, value.Values) }) } // ---------------------------- // Serialization/Deserialization Tests // ---------------------------- func TestValueBSONSerialization(t *testing.T) { t.Run("string value BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"string": "hello world"}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.PermissionBound, restored.PermissionBound) assert.Equal(t, original.Target, restored.Target) assert.Equal(t, original.Type, restored.Type) assert.Equal(t, original.Cardinality, restored.Cardinality) assert.Equal(t, original.PropertySchemaRef, restored.PropertySchemaRef) assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value str, err := restored.AsString() require.NoError(t, err) assert.Equal(t, "hello world", str) }) t.Run("strings value BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"strings": []string{"hello", "world", "test"}}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values strings, err := restored.AsStrings() require.NoError(t, err) assert.Equal(t, []string{"hello", "world", "test"}, strings) }) t.Run("integer value BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTInteger, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"integer": int64(42)}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value val, err := restored.AsInteger() require.NoError(t, err) assert.Equal(t, int64(42), val) }) t.Run("float value BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTFloat, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"float": 3.14159}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value val, err := restored.AsFloat() require.NoError(t, err) assert.Equal(t, 3.14159, val) }) t.Run("datetime value BSON round-trip", func(t *testing.T) { // Use millisecond precision to match primitive.DateTime now := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC).Truncate(time.Millisecond) original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTDateTime, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"date_time": now}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare all fields except Values (which may have timezone differences) assert.Equal(t, original.PermissionBound, restored.PermissionBound) assert.Equal(t, original.Target, restored.Target) assert.Equal(t, original.Type, restored.Type) assert.Equal(t, original.Cardinality, restored.Cardinality) assert.Equal(t, original.PropertySchemaRef, restored.PropertySchemaRef) // Test that we can still access the value val, err := restored.AsDateTime() require.NoError(t, err) // Compare only the actual time value, not timezone metadata assert.Equal(t, now.Unix(), val.Unix()) }) t.Run("monetary value BSON round-trip", func(t *testing.T) { money := Money{ Amount: primitive.NewDecimal128(10000, 0), // $100.00 Currency: CurrencyUSD, } original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTMonetary, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"monetary": money}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value val, err := restored.AsMonetary() require.NoError(t, err) assert.Equal(t, money, val) }) t.Run("reference value BSON round-trip", func(t *testing.T) { ref := primitive.NewObjectID() original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTReference, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"reference": ref}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value val, err := restored.AsReference() require.NoError(t, err) assert.Equal(t, ref, val) }) t.Run("color value BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTColor, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"color": "#FF0000"}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value val, err := restored.AsColor() require.NoError(t, err) assert.Equal(t, "#FF0000", val) }) t.Run("object value BSON round-trip", func(t *testing.T) { obj := Object{ "field1": Value{ Type: PTString, Cardinality: One, Values: SettingsT{"string": "value1"}, }, "field2": Value{ Type: PTInteger, Cardinality: One, Values: SettingsT{"integer": int64(42)}, }, } original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTObject, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"object": obj}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value val, err := restored.AsObject() require.NoError(t, err) assert.Equal(t, obj, val) }) } func TestValueJSONSerialization(t *testing.T) { t.Run("string value JSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: One, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"string": "hello world"}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.PermissionBound, restored.PermissionBound) assert.Equal(t, original.Target, restored.Target) assert.Equal(t, original.Type, restored.Type) assert.Equal(t, original.Cardinality, restored.Cardinality) assert.Equal(t, original.PropertySchemaRef, restored.PropertySchemaRef) assert.Equal(t, original.Values, restored.Values) // Test that we can still access the value str, err := restored.AsString() require.NoError(t, err) assert.Equal(t, "hello world", str) }) t.Run("integers value JSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTInteger, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"integers": []int64{1, 2, 3, 4, 5}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values vals, err := restored.AsIntegers() require.NoError(t, err) assert.Equal(t, []int64{1, 2, 3, 4, 5}, vals) }) t.Run("floats value JSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTFloat, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"floats": []float64{1.1, 2.2, 3.3}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values vals, err := restored.AsFloats() require.NoError(t, err) assert.Equal(t, []float64{1.1, 2.2, 3.3}, vals) }) t.Run("datetimes value JSON round-trip", func(t *testing.T) { // Use millisecond precision to match primitive.DateTime now1 := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC).Truncate(time.Millisecond) now2 := time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC).Truncate(time.Millisecond) original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTDateTime, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"date_times": []time.Time{now1, now2}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare all fields except Values (which may have timezone differences) assert.Equal(t, original.PermissionBound, restored.PermissionBound) assert.Equal(t, original.Target, restored.Target) assert.Equal(t, original.Type, restored.Type) assert.Equal(t, original.Cardinality, restored.Cardinality) assert.Equal(t, original.PropertySchemaRef, restored.PropertySchemaRef) // Test that we can still access the values vals, err := restored.AsDateTimes() require.NoError(t, err) // Compare only the actual time values, not timezone metadata assert.Equal(t, 2, len(vals)) assert.Equal(t, now1.Unix(), vals[0].Unix()) assert.Equal(t, now2.Unix(), vals[1].Unix()) }) t.Run("monetaries value JSON round-trip", func(t *testing.T) { money1 := Money{Amount: primitive.NewDecimal128(10000, 0), Currency: CurrencyUSD} money2 := Money{Amount: primitive.NewDecimal128(20000, 0), Currency: CurrencyUSD} original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTMonetary, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"monetaries": []Money{money1, money2}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values vals, err := restored.AsMonetaries() require.NoError(t, err) assert.Equal(t, []Money{money1, money2}, vals) }) t.Run("references value JSON round-trip", func(t *testing.T) { ref1 := primitive.NewObjectID() ref2 := primitive.NewObjectID() original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTReference, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"references": []primitive.ObjectID{ref1, ref2}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values vals, err := restored.AsReferences() require.NoError(t, err) assert.Equal(t, []primitive.ObjectID{ref1, ref2}, vals) }) t.Run("colors value JSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTColor, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"colors": []string{"#FF0000", "#00FF00", "#0000FF"}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values vals, err := restored.AsColors() require.NoError(t, err) assert.Equal(t, []string{"#FF0000", "#00FF00", "#0000FF"}, vals) }) t.Run("objects value JSON round-trip", func(t *testing.T) { obj1 := Object{"field1": Value{Type: PTString, Cardinality: One, Values: SettingsT{"string": "value1"}}} obj2 := Object{"field2": Value{Type: PTInteger, Cardinality: One, Values: SettingsT{"integer": int64(42)}}} original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTObject, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"objects": []Object{obj1, obj2}}, } // Marshal to JSON jsonData, err := bson.MarshalExtJSON(original, true, false) require.NoError(t, err) // Unmarshal from JSON var restored Value err = bson.UnmarshalExtJSON(jsonData, true, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the values vals, err := restored.AsObjects() require.NoError(t, err) assert.Equal(t, []Object{obj1, obj2}, vals) }) } func TestValueSerializationEdgeCases(t *testing.T) { t.Run("empty values BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"strings": []string{}}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the empty values vals, err := restored.AsStrings() require.NoError(t, err) assert.Equal(t, []string{}, vals) }) t.Run("nil values BSON round-trip", func(t *testing.T) { original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"strings": []string(nil)}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the nil values vals, err := restored.AsStrings() require.NoError(t, err) assert.Nil(t, vals) }) t.Run("large values BSON round-trip", func(t *testing.T) { // Create a large slice of strings largeStrings := make([]string, 1000) for i := 0; i < 1000; i++ { largeStrings[i] = fmt.Sprintf("string_%d", i) } original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"strings": largeStrings}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the large values vals, err := restored.AsStrings() require.NoError(t, err) assert.Equal(t, largeStrings, vals) }) t.Run("special characters BSON round-trip", func(t *testing.T) { specialStrings := []string{ "hello world", "привет мир", // Cyrillic "你好世界", // Chinese "مرحبا بالعالم", // Arabic "🚀🌟💫", // Emojis "line\nbreak\ttab", "quotes\"and'apostrophes", "backslash\\and/slash", } original := Value{ PermissionBound: createTestPermissionBound(), Target: createTestRecordRef(), Type: PTString, Cardinality: Many, PropertySchemaRef: primitive.NewObjectID(), Values: SettingsT{"strings": specialStrings}, } // Marshal to BSON bsonData, err := bson.Marshal(original) require.NoError(t, err) // Unmarshal from BSON var restored Value err = bson.Unmarshal(bsonData, &restored) require.NoError(t, err) // Compare assert.Equal(t, original.Values, restored.Values) // Test that we can still access the special character values vals, err := restored.AsStrings() require.NoError(t, err) assert.Equal(t, specialStrings, vals) }) }