| package meter_test |
|
|
| import ( |
| "testing" |
| "time" |
|
|
| "github.com/oklog/ulid/v2" |
| "github.com/samber/lo" |
| "github.com/stretchr/testify/assert" |
|
|
| "github.com/openmeterio/openmeter/openmeter/meter" |
| "github.com/openmeterio/openmeter/pkg/models" |
| ) |
|
|
| func TestParseEvent(t *testing.T) { |
| meterSum := meter.Meter{ |
| ManagedResource: models.ManagedResource{ |
| ID: ulid.Make().String(), |
| NamespacedModel: models.NamespacedModel{ |
| Namespace: "default", |
| }, |
| ManagedModel: models.ManagedModel{ |
| CreatedAt: time.Now(), |
| UpdatedAt: time.Now(), |
| }, |
| Name: "Test meter", |
| }, |
| Key: "m1", |
| Aggregation: meter.MeterAggregationSum, |
| EventType: "api-calls", |
| ValueProperty: lo.ToPtr("$.duration_ms"), |
| GroupBy: map[string]string{ |
| "method": "$.method", |
| "path": "$.path", |
| }, |
| } |
|
|
| meterCount := meter.Meter{ |
| ManagedResource: models.ManagedResource{ |
| ID: ulid.Make().String(), |
| NamespacedModel: models.NamespacedModel{ |
| Namespace: "default", |
| }, |
| ManagedModel: models.ManagedModel{ |
| CreatedAt: time.Now(), |
| UpdatedAt: time.Now(), |
| }, |
| Name: "Test meter", |
| }, |
| Key: "m2", |
| Aggregation: meter.MeterAggregationCount, |
| EventType: "api-calls", |
| } |
|
|
| meterUniqueCount := meter.Meter{ |
| ManagedResource: models.ManagedResource{ |
| ID: ulid.Make().String(), |
| NamespacedModel: models.NamespacedModel{ |
| Namespace: "default", |
| }, |
| ManagedModel: models.ManagedModel{ |
| CreatedAt: time.Now(), |
| UpdatedAt: time.Now(), |
| }, |
| Name: "Test meter", |
| }, |
| Key: "m3", |
| Aggregation: meter.MeterAggregationUniqueCount, |
| EventType: "spans", |
| ValueProperty: lo.ToPtr("$.trace_id"), |
| } |
|
|
| tests := []struct { |
| description string |
| meter meter.Meter |
| data []byte |
| err error |
| errString string |
| value *float64 |
| valueStr *string |
| groupBy map[string]string |
| }{ |
| { |
| description: "should parse event", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": 100, "method": "GET", "path": "/api/v1"}`), |
| value: lo.ToPtr(100.0), |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should parse event with numeric string value", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": "100", "method": "GET", "path": "/api/v1"}`), |
| value: lo.ToPtr(100.0), |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should parse count as value one", |
| meter: meterCount, |
| data: []byte(`{}`), |
| value: lo.ToPtr(1.0), |
| groupBy: map[string]string{}, |
| }, |
| { |
| description: "should parse unique count as string", |
| meter: meterUniqueCount, |
| data: []byte(`{"trace_id": "test_trace_id"}`), |
| valueStr: lo.ToPtr("test_trace_id"), |
| groupBy: map[string]string{}, |
| }, |
| { |
| description: "should parse event with missing group by properties", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": 100}`), |
| value: lo.ToPtr(100.0), |
| groupBy: map[string]string{ |
| "method": "", |
| "path": "", |
| }, |
| }, |
| { |
| description: "should return error with invalid json", |
| meter: meterSum, |
| data: []byte(`{`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: failed to parse event data: unexpected end of JSON input", |
| groupBy: map[string]string{}, |
| }, |
| { |
| description: "should return error with data missing", |
| meter: meterSum, |
| data: []byte(`null`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: null and missing value property", |
| groupBy: map[string]string{ |
| "method": "", |
| "path": "", |
| }, |
| }, |
| { |
| description: "should return error with data null", |
| meter: meterSum, |
| data: []byte(`null`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: null and missing value property", |
| groupBy: map[string]string{ |
| "method": "", |
| "path": "", |
| }, |
| }, |
| { |
| description: "should return error with value property not found", |
| meter: meterSum, |
| data: []byte(`{"method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidEvent{}, |
| errString: `invalid event: missing value property: "$.duration_ms"`, |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should return error when value property is null", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": null, "method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: value cannot be null", |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should return error when value property is NaN", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": "NaN", "method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: value cannot be NaN", |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should return error when value property is infinity", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": "Inf", "method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: value cannot be infinity", |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should return error when value property is postiive infinity", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": "+Inf", "method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: value cannot be infinity", |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should return error when value property is negative infinity", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": "-Inf", "method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidEvent{}, |
| errString: "invalid event: value cannot be infinity", |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| { |
| description: "should return error when value property cannot be parsed as number", |
| meter: meterSum, |
| data: []byte(`{"duration_ms": "not a number", "method": "GET", "path": "/api/v1"}`), |
| err: meter.ErrInvalidMeter{}, |
| errString: "invalid event: value cannot be parsed as float64: not a number", |
| groupBy: map[string]string{ |
| "method": "GET", |
| "path": "/api/v1", |
| }, |
| }, |
| } |
|
|
| for _, test := range tests { |
| t.Run(test.description, func(t *testing.T) { |
| parsedEvent, err := meter.ParseEvent(test.meter, test.data) |
|
|
| if test.err == nil { |
| assert.NoError(t, err) |
| } else { |
| assert.ErrorAs(t, err, &test.err) |
| assert.EqualError(t, err, test.errString) |
| } |
|
|
| assert.Equal(t, test.value, parsedEvent.Value) |
| assert.Equal(t, test.valueStr, parsedEvent.ValueString) |
| assert.Equal(t, test.groupBy, parsedEvent.GroupBy) |
| }) |
| } |
| } |
|
|