diff --git a/lib/debezium/converters/decimal_test.go b/lib/debezium/converters/decimal_test.go index b617b67fd..3b051620d 100644 --- a/lib/debezium/converters/decimal_test.go +++ b/lib/debezium/converters/decimal_test.go @@ -3,6 +3,7 @@ package converters import ( "testing" + "github.com/artie-labs/transfer/lib/typing/decimal" "github.com/stretchr/testify/assert" ) @@ -49,265 +50,175 @@ func TestToBytes(t *testing.T) { } } -func TestField_DecodeDecimal(t *testing.T) { - testCases := []struct { - name string - encoded string - params map[string]any +func TestDecimal_Convert(t *testing.T) { + { + // Numeric (5, 0) + converter := NewDecimal(5, 0, false) + val, err := converter.Convert([]byte("BQ==")) + assert.NoError(t, err) - expectedValue string - expectedPrecision int32 - expectNilPtrPrecision bool - expectedScale int32 - expectedErr string - }{ - { - name: "No scale (nil map)", - expectedErr: "object is empty", - }, - { - name: "No scale (not provided)", - params: map[string]any{ - "connect.decimal.precision": "5", - }, - expectedErr: "key: scale does not exist in object", - }, - { - name: "Precision is not an integer", - params: map[string]any{ - "scale": "2", - "connect.decimal.precision": "abc", - }, - expectedErr: "key: connect.decimal.precision is not type integer", - }, - { - name: "NUMERIC(5,0)", - encoded: "BQ==", - params: map[string]any{ - "scale": "0", - "connect.decimal.precision": "5", - }, - expectedValue: "5", - expectedPrecision: 5, - expectedScale: 0, - }, - { - name: "NUMERIC(5,2)", - encoded: "AOHJ", - params: map[string]any{ - "scale": "2", - "connect.decimal.precision": "5", - }, - expectedValue: "578.01", - expectedPrecision: 5, - expectedScale: 2, - }, - { - name: "NUMERIC(38, 0) - small #", - encoded: "Ajc=", - params: map[string]any{ - "scale": "0", - "connect.decimal.precision": "38", - }, - expectedValue: "567", - expectedPrecision: 38, - expectedScale: 0, - }, - { - name: "NUMERIC(38, 0) - large #", - encoded: "SztMqFqGxHoJiiI//////w==", - params: map[string]any{ - "scale": "0", - "connect.decimal.precision": "38", - }, - expectedValue: "99999999999999999999999999999999999999", - expectedPrecision: 38, - expectedScale: 0, - }, - { - name: "NUMERIC(38, 2) - small #", - encoded: "DPk=", - params: map[string]any{ - "scale": "2", - "connect.decimal.precision": "38", - }, - expectedValue: "33.21", - expectedPrecision: 38, - expectedScale: 2, - }, - { - name: "NUMERIC(38, 2) - large #", - encoded: "AMCXznvJBxWzS58P/////w==", - params: map[string]any{ - "scale": "2", - "connect.decimal.precision": "38", - }, - expectedValue: "9999999999999999999999999999999999.99", - expectedPrecision: 38, - expectedScale: 2, - }, - { - name: "NUMERIC(38, 4) - small #", - encoded: "SeuD", - params: map[string]any{ - "scale": "4", - "connect.decimal.precision": "38", - }, - expectedValue: "484.4419", - expectedPrecision: 38, - expectedScale: 4, - }, - { - name: "NUMERIC(38, 4) - large #", - encoded: "Ae0Jvq2HwDeNjmP/////", - params: map[string]any{ - "scale": "4", - "connect.decimal.precision": "38", - }, - expectedValue: "999999999999999999999999999999.9999", - expectedPrecision: 38, - expectedScale: 4, - }, - { - name: "NUMERIC(39,4) - small #", - encoded: "AKQQ", - params: map[string]any{ - "scale": "4", - "connect.decimal.precision": "39", - }, - expectedValue: "4.2000", - expectedPrecision: 39, - expectedScale: 4, - }, - { - name: "NUMERIC(39,4) - large # ", - encoded: "AuM++mE16PeIpWp/trI=", - params: map[string]any{ - "scale": "4", - "connect.decimal.precision": "39", - }, - expectedValue: "5856910285916918584382586878.1234", - expectedPrecision: 39, - expectedScale: 4, - }, - { - name: "MONEY", - encoded: "ALxhYg==", - params: map[string]any{ - "scale": "2", - }, - expectedValue: "123456.98", - expectedPrecision: -1, - expectedScale: 2, - }, + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "5", dec.String()) } + { + // Numeric (5, 2) + converter := NewDecimal(5, 2, false) + val, err := converter.Convert([]byte("AOHJ")) + assert.NoError(t, err) - for _, testCase := range testCases { - field := Field{ - Parameters: testCase.params, - } + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "578.01", dec.String()) + } + { + // Numeric (38, 0) - Small # + converter := NewDecimal(38, 0, false) + val, err := converter.Convert([]byte("Ajc=")) + assert.NoError(t, err) - bytes, err := toBytes(testCase.encoded) + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "567", dec.String()) + } + { + // Numeric (38, 0) - Large # + converter := NewDecimal(38, 0, false) + val, err := converter.Convert([]byte("SztMqFqGxHoJiiI//////w==")) assert.NoError(t, err) - dec, err := field.DecodeDecimal(bytes) - if testCase.expectedErr != "" { - assert.ErrorContains(t, err, testCase.expectedErr, testCase.name) - continue - } + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "99999999999999999999999999999999999999", dec.String()) + } + { + // Numeric (38, 2) - Small # + converter := NewDecimal(38, 2, false) + val, err := converter.Convert([]byte("DPk=")) + assert.NoError(t, err) + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "33.21", dec.String()) + } + { + // Numeric (38, 2) - Large # + converter := NewDecimal(38, 2, false) + val, err := converter.Convert([]byte("AMCXznvJBxWzS58P/////w==")) assert.NoError(t, err) - assert.Equal(t, testCase.expectedValue, dec.String(), testCase.name) - assert.Equal(t, testCase.expectedPrecision, dec.Details().Precision(), testCase.name) - assert.Equal(t, testCase.expectedScale, dec.Details().Scale(), testCase.name) + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "9999999999999999999999999999999999.99", dec.String()) } -} + { + // Numeric (38, 4) - Small # + converter := NewDecimal(38, 4, false) + val, err := converter.Convert([]byte("SeuD")) + assert.NoError(t, err) -func TestField_DecodeDebeziumVariableDecimal(t *testing.T) { - type _testCase struct { - name string - value any + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "484.4419", dec.String()) + } + { + // Numeric (38, 4) - Large # + converter := NewDecimal(38, 4, false) + val, err := converter.Convert([]byte("Ae0Jvq2HwDeNjmP/////")) + assert.NoError(t, err) - expectedValue string - expectedScale int32 - expectedErr string + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "999999999999999999999999999999.9999", dec.String()) } + { + // Numeric (39, 4) - Small # + converter := NewDecimal(39, 4, false) + val, err := converter.Convert([]byte("AKQQ")) + assert.NoError(t, err) - testCases := []_testCase{ - { - name: "empty val (nil)", - expectedErr: "value is not map[string]any type", - }, - { - name: "empty map", - value: map[string]any{}, - expectedErr: "object is empty", - }, - { - name: "scale is not an integer", - value: map[string]any{ - "scale": "foo", - }, - expectedErr: "key: scale is not type integer", - }, - { - name: "value exists (scale 3)", - value: map[string]any{ - "scale": 3, - "value": "SOx4FQ==", - }, - expectedValue: "1223456.789", - expectedScale: 3, - }, - { - name: "value exists (scale 2)", - value: map[string]any{ - "scale": 2, - "value": "MDk=", - }, - expectedValue: "123.45", - expectedScale: 2, - }, - { - name: "negative numbers (scale 7)", - value: map[string]any{ - "scale": 7, - "value": "wT9Wmw==", - }, - expectedValue: "-105.2813669", - expectedScale: 7, - }, - { - name: "malformed base64 value", - value: map[string]any{ - "scale": 7, - "value": "==wT9Wmw==", - }, - expectedErr: "failed to base64 decode", - }, - { - name: "[]byte value", - value: map[string]any{ - "scale": 7, - "value": []byte{193, 63, 86, 155}, - }, - expectedValue: "-105.2813669", - expectedScale: 7, - }, + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "4.2000", dec.String()) } + { + // Numeric (39, 4) - Large # + converter := NewDecimal(39, 4, false) + val, err := converter.Convert([]byte("AuM++mE16PeIpWp/trI=")) + assert.NoError(t, err) - for _, testCase := range testCases { - field := Field{} - dec, err := field.DecodeDebeziumVariableDecimal(testCase.value) - if testCase.expectedErr != "" { - assert.ErrorContains(t, err, testCase.expectedErr, testCase.name) - continue - } + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "5856910285916918584382586878.1234", dec.String()) + } + { + // Money + converter := NewDecimal(19, 4, false) + val, err := converter.Convert([]byte("ALxhYg==")) + assert.NoError(t, err) + + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "123456.98", dec.String()) + } +} - assert.Equal(t, int32(-1), dec.Details().Precision(), testCase.name) - assert.Equal(t, testCase.expectedScale, dec.Details().Scale(), testCase.name) - assert.Equal(t, testCase.expectedValue, dec.String(), testCase.name) +func TestVariableDecimal_Convert(t *testing.T) { + converter := NewDecimal(decimal.PrecisionNotSpecified, decimal.DefaultScale, true) + { + // Variable Numeric, scale 3 + val, err := converter.Convert(map[string]any{ + "scale": 3, + "value": "SOx4FQ==", + }) + assert.NoError(t, err) + + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "1223456.789", dec.String()) + } + { + // Variable Numeric, scale 2 + val, err := converter.Convert(map[string]any{ + "scale": 2, + "value": "MDk=", + }) + assert.NoError(t, err) + + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "123.45", dec.String()) } + { + // Variable Numeric, scale 7 + val, err := converter.Convert(map[string]any{ + "scale": 7, + "value": "wT9Wmw==", + }) + assert.NoError(t, err) + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "-105.2813669", dec.String()) + } + { + // Malformed b64 value + _, err := converter.Convert(map[string]any{ + "scale": 7, + "value": "==wT9Wmw==", + }) + assert.ErrorContains(t, err, "failed to base64 decode") + } + { + // []byte value + val, err := converter.Convert(map[string]any{ + "scale": 7, + "value": []byte{193, 63, 86, 155}, + }) + assert.NoError(t, err) + + dec, isOk := val.(*decimal.Decimal) + assert.True(t, isOk) + assert.Equal(t, "-105.2813669", dec.String()) + } }