Repository: GreenmaskIO/greenmask (go)

Issue:
bug: NoiseDate max/min error?
Hi!

I play with playground (launched with docker compoose, version v0.2.11) and date noising, I think there is a bug on mix/max value (or my comprehension is uncorrect).

Trying to apply the [documentation example](https://docs.greenmask.io/latest/built_in_transformers/standard_transformers/noise_date/?h=noisedate#example-adding-noise-to-the-modified-date-with-dynamic-min-parameter-with-hash-engine) with a huge ratio:
```yaml
      transformers:
        - name: "NoiseDate"
          params:
            column: "hiredate"
            max_ratio: "100 year 2 mons 3 day 04:05:06.07"
            truncate: "month"
            max: "2020-01-01"
            engine: "hash"
          dynamic_params:
            min:
              column: "birthdate"q
              # default: "birthdate" - This documented value isn't valid anymore --> "[...] has invalid keys: default"
```
([Just add this in playground conf file](https://github.com/GreenmaskIO/greenmask/blob/4d539d6f7f6e54cac12361def814a071f710510f/playground/config.yml))

If I understand correctly:
- original date will be modified and can be ~100 year before or after
- maximal date will be 2020-01-01
- minimal date will be the birthdate

I have this kind of result:
```
$ greenmask validate --config config.yml --data --diff


        "humanresources"."employee"
+-----------+------------------+----------------+-------------------+
| %LineNum% | Column           | OriginalValue  | TransformedValue  |
+-----------+------------------+----------------+-------------------+
| 0         | birthdate        | 1969-01-29     | 1969-01-29        |
+           +------------------+----------------+-------------------+
|           | hiredate         | 2009-01-14     | 2052-05-01        |
+-----------+------------------+----------------+-------------------+
```

Another simpliest try:
```yaml
      transformers: # List of transformers to apply
        - name: "NoiseDate" # name of transformers
          params: # Transformer parameters
            max_ratio: "10 year 9 mon 1 day"
            column: "birthdate" # Column parameter - this transformer affects scheduled_departure column
            max: '2020-01-01'
            min: "1930-01-02"
```

Result:
```sh
$ greenmask validate --config config.yml --data --diff  --transformed-only


        "humanresources"."employee"
+-----------+------------------+---------------+------------------+
| %LineNum% | Column           | OriginalValue | TransformedValue |
+-----------+------------------+---------------+------------------+
| 0         | businessentityid | 1             | 1                |
+           +------------------+---------------+------------------+
|           | birthdate        | 1969-01-29    | 2020-01-01       |
+-----------+------------------+---------------+------------------+
| 1         | businessentityid | 2             | 2                |
+           +------------------+---------------+------------------+
|           | birthdate        | 1971-08-01    | 2020-01-01       |
+-----------+------------------+---------------+------------------+
[...]
All date are at the maximal value
```

I have a misundestending of the documentation, or this is a real bug?

Pull request:
Fix: Fixed dynamic mode determination for NoiseDate
* Fixed dynamic mode determination for NoiseDate
* Added unit tests for dynamic mode

Closes #303 

Patch:
diff --git a/internal/db/postgres/transformers/noise_date.go b/internal/db/postgres/transformers/noise_date.go
index f49de6f..1316b70 100644
--- a/internal/db/postgres/transformers/noise_date.go
+++ b/internal/db/postgres/transformers/noise_date.go
@@ -101,7 +101,8 @@ type NoiseDateTransformer struct {
 	engineParam   toolkit.Parameterizer
 	truncateParam toolkit.Parameterizer
 
-	transform func(time.Time) (time.Time, error)
+	dynamicMode bool
+	transform   func(time.Time) (time.Time, error)
 }
 
 type noiseTimestampMinMaxEncoder func(toolkit.Parameterizer, toolkit.Parameterizer) (*time.Time, *time.Time, error)
@@ -242,6 +243,7 @@ func NewNoiseDateTransformerBase(ctx context.Context, driver *toolkit.Driver, pa
 		minParam:      minParam,
 		engineParam:   engineParam,
 		truncateParam: truncateParam,
+		dynamicMode:   dynamicMode,
 	}, nil, nil
 }
 
@@ -254,7 +256,7 @@ func (ndt *NoiseDateTransformer) GetAffectedColumns() map[int]string {
 }
 
 func (ndt *NoiseDateTransformer) Init(ctx context.Context) error {
-	if ndt.columnParam.IsDynamic() {
+	if ndt.dynamicMode {
 		ndt.transform = ndt.dynamicTransform
 	}
 	return nil
diff --git a/pkg/toolkit/tesing_helpers.go b/pkg/toolkit/tesing_helpers.go
index 25738ef..7577f28 100644
--- a/pkg/toolkit/tesing_helpers.go
+++ b/pkg/toolkit/tesing_helpers.go
@@ -202,7 +202,7 @@ var columnList = []*Column{
 		Name:     "ip_address",
 		TypeName: "inet",
 		TypeOid:  pgtype.InetOID,
-		Num:      22,
+		Num:      23,
 		NotNull:  false,
 		Length:   -1,
 	},
@@ -210,7 +210,23 @@ var columnList = []*Column{
 		Name:     "net_mask",
 		TypeName: "cidr",
 		TypeOid:  pgtype.CIDROID,
-		Num:      23,
+		Num:      24,
+		NotNull:  false,
+		Length:   -1,
+	},
+	{
+		Name:     "hiredate",
+		TypeName: "timestamp",
+		TypeOid:  pgtype.TimestampOID,
+		Num:      25,
+		NotNull:  false,
+		Length:   -1,
+	},
+	{
+		Name:     "birthdate",
+		TypeName: "timestamp",
+		TypeOid:  pgtype.TimestampOID,
+		Num:      26,
 		NotNull:  false,
 		Length:   -1,
 	},


Test patch:
diff --git a/internal/db/postgres/transformers/noise_date_test.go b/internal/db/postgres/transformers/noise_date_test.go
index 35cd2ec..00e1cb6 100644
--- a/internal/db/postgres/transformers/noise_date_test.go
+++ b/internal/db/postgres/transformers/noise_date_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/greenmaskio/greenmask/internal/db/postgres/transformers/utils"
 	"github.com/greenmaskio/greenmask/pkg/toolkit"
 )
 
@@ -151,3 +152,82 @@ func TestNoiseDateTransformer_Transform(t *testing.T) {
 		})
 	}
 }
+
+func TestNoiseDateTransformer_Transform_dynamic(t *testing.T) {
+	type expected struct {
+		min time.Time
+		max time.Time
+	}
+
+	tests := []struct {
+		name          string
+		columnName    string
+		params        map[string]toolkit.ParamsValue
+		dynamicParams map[string]*toolkit.DynamicParamValue
+		record        map[string]*toolkit.RawValue
+		expected      expected
+	}{
+		{
+			name:       "NoiseDate with dynamic min",
+			columnName: "hiredate",
+			record: map[string]*toolkit.RawValue{
+				"hiredate":  toolkit.NewRawValue([]byte("2009-01-01 00:00:00"), false),
+				"birthdate": toolkit.NewRawValue([]byte("1996-01-01 00:00:00"), false),
+			},
+			params: map[string]toolkit.ParamsValue{
+				"max_ratio": toolkit.ParamsValue("100 year 2 mons 3 day 04:05:06.07"),
+				"truncate":  toolkit.ParamsValue("month"),
+				"max":       toolkit.ParamsValue("2020-01-01 00:00:00"),
+				"engine":    toolkit.ParamsValue("hash"),
+			},
+			dynamicParams: map[string]*toolkit.DynamicParamValue{
+				"min": {
+					Column: "birthdate",
+				},
+			},
+			expected: expected{
+				min: time.Date(1996, 1, 1, 0, 0, 0, 0, time.UTC),
+				max: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			driver, record := toolkit.GetDriverAndRecord(tt.record)
+
+			tt.params["column"] = toolkit.ParamsValue(tt.columnName)
+			def, ok := utils.DefaultTransformerRegistry.Get("NoiseDate")
+			require.True(t, ok)
+
+			transformer, warnings, err := def.Instance(
+				context.Background(),
+				driver,
+				tt.params,
+				tt.dynamicParams,
+				"",
+			)
+			require.NoError(t, err)
+			require.Empty(t, warnings)
+
+			err = transformer.Transformer.Init(context.Background())
+			require.NoError(t, err)
+
+			for _, dp := range transformer.DynamicParameters {
+				dp.SetRecord(record)
+			}
+
+			r, err := transformer.Transformer.Transform(
+				context.Background(),
+				record,
+			)
+			require.NoError(t, err)
+
+			var res time.Time
+			empty, err := r.ScanColumnValueByName(tt.columnName, &res)
+			require.False(t, empty)
+			require.NoError(t, err)
+			require.True(t, res.After(tt.expected.min) && res.Before(tt.expected.max))
+		})
+	}
+}