Files
sendico/api/pkg/model/value_test.go
Stephan D 62a6631b9a
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
service backend
2025-11-07 18:35:26 +01:00

1398 lines
40 KiB
Go

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)
})
}