modelRT/handler/data_object_attribute_query...

274 lines
8.6 KiB
Go

package handler
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"modelRT/common"
"modelRT/constants"
"modelRT/orm"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseDataObjectAttributeQuery(t *testing.T) {
const token = "nspath.component.bay.measurement"
tests := []struct {
name string
target string
wantToken string
wantField string
wantErr string
}{
{
name: "reads token and field from query parameters",
target: "/data-object/attribute?token=" + token + "&field=NAME",
wantToken: token,
wantField: "name",
},
{
name: "defaults missing field to value",
target: "/data-object/attribute?token=" + token,
wantToken: token,
wantField: "value",
},
{
name: "defaults empty field to value",
target: "/data-object/attribute?token=" + token + "&field=",
wantToken: token,
wantField: "value",
},
{
name: "rejects missing token",
target: "/data-object/attribute?field=value",
wantErr: "token is missing from query parameters",
},
{
name: "rejects empty token",
target: "/data-object/attribute?token=&field=value",
wantErr: "token is missing from query parameters",
},
{
name: "does not read legacy path parameters",
target: "/data-object/attribute/" + token + "/value",
wantErr: "token is missing from query parameters",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
ctx.Request = httptest.NewRequest(http.MethodGet, tt.target, nil)
token, field, err := parseDataObjectAttributeQuery(ctx)
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
assert.Empty(t, token)
assert.Empty(t, field)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantToken, token)
assert.Equal(t, tt.wantField, field)
})
}
}
func TestValidateDataObjectField(t *testing.T) {
tests := []struct {
name string
token string
dataObjectType constants.DataObjectType
field string
wantErr error
}{
{name: "bay value", token: "nspath.component.bay.measurement", dataObjectType: constants.DataObjectTypeMeasurement, field: "value"},
{name: "bay name", token: "nspath.component.bay.measurement", dataObjectType: constants.DataObjectTypeMeasurement, field: "name"},
{name: "bay binding", token: "nspath.component.bay.measurement", dataObjectType: constants.DataObjectTypeMeasurement, field: "binding"},
{name: "bay field is case insensitive", token: "nspath.component.bay.measurement", dataObjectType: constants.DataObjectTypeMeasurement, field: "DATA_SOURCE"},
{name: "bay unsupported", token: "nspath.component.bay.measurement", dataObjectType: constants.DataObjectTypeMeasurement, field: "unknown", wantErr: common.ErrUnsupportedMeasurementField},
{name: "parameter value", token: "nspath.component.rated.attribute", dataObjectType: constants.DataObjectTypeParameter, field: "value"},
{name: "parameter name", token: "nspath.component.rated.attribute", dataObjectType: constants.DataObjectTypeParameter, field: "name"},
{name: "parameter rejects mode", token: "nspath.component.rated.attribute", dataObjectType: constants.DataObjectTypeParameter, field: "mode", wantErr: common.ErrUnsupportedParameterField},
{name: "component name", token: "nspath.component.component.name", dataObjectType: constants.DataObjectTypeParameter, field: "name"},
{name: "component rejects mode", token: "nspath.component.component.name", dataObjectType: constants.DataObjectTypeParameter, field: "mode", wantErr: common.ErrUnsupportedParameterField},
{name: "parameter rejects size", token: "nspath.component.rated.attribute", dataObjectType: constants.DataObjectTypeParameter, field: "size", wantErr: common.ErrUnsupportedParameterField},
{name: "invalid type", token: "token", dataObjectType: constants.DataObjectType("unknown"), field: "value", wantErr: assert.AnError},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateDataObjectField(tt.dataObjectType, tt.field)
if tt.wantErr == nil {
require.NoError(t, err)
return
}
require.Error(t, err)
if tt.wantErr != assert.AnError {
assert.ErrorIs(t, err, tt.wantErr)
}
})
}
}
func TestQueryParameterAttributeValueFromRedisHash(t *testing.T) {
fields := map[string]string{
"value": "220.50",
"type": "DOUBLE PRECISION",
"name": "110kV_TV.cable_22.rated.rated_voltage",
"description": "额定电压",
}
loader := hashFieldLoaderForTest(fields)
value, err := queryDataObjectAttributeValue(
context.Background(),
constants.DataObjectTypeParameter,
"parameter-token",
"value",
loader,
nil,
nil,
)
require.NoError(t, err)
assert.Equal(t, json.Number("220.50"), value)
description, err := queryDataObjectAttributeValue(
context.Background(),
constants.DataObjectTypeParameter,
"parameter-token",
"description",
loader,
nil,
nil,
)
require.NoError(t, err)
assert.Equal(t, "额定电压", description)
}
func TestQueryMeasurementAttributeValueFromRedisHash(t *testing.T) {
fields := map[string]string{
"mode": "1",
"size": "10",
"name": "110kV_TV.IA_rms",
"data_source": `{"type":1,"io_address":{"channel":"tm1p"}}`,
"event_plan": `{"enabled":true}`,
}
loader := hashFieldLoaderForTest(fields)
mode, err := queryDataObjectAttributeValue(
context.Background(),
constants.DataObjectTypeMeasurement,
"measurement-token",
"mode",
loader,
nil,
nil,
)
require.NoError(t, err)
assert.Equal(t, int16(1), mode)
eventPlan, err := queryDataObjectAttributeValue(
context.Background(),
constants.DataObjectTypeMeasurement,
"measurement-token",
"event_plan",
loader,
nil,
nil,
)
require.NoError(t, err)
assert.Equal(t, map[string]any{"enabled": true}, eventPlan)
}
func TestQueryMeasurementRealtimeValueUsesDataSourceFromRedisHash(t *testing.T) {
dataSource := orm.JSONMap{
"type": float64(1),
"io_address": map[string]any{
"station": "001",
"channel": "tm1p",
},
}
metadataLoader := func(_ context.Context, token string) (orm.JSONMap, int, error) {
assert.Equal(t, "measurement-token", token)
return dataSource, 2, nil
}
valueLoader := func(_ context.Context, dataSource orm.JSONMap, size int) (any, error) {
assert.Equal(t, float64(1), dataSource["type"])
assert.Equal(t, "001", dataSource["io_address"].(map[string]any)["station"])
assert.Equal(t, 2, size)
return []float64{220, 219.5}, nil
}
value, err := queryDataObjectAttributeValue(
context.Background(),
constants.DataObjectTypeMeasurement,
"measurement-token",
"value",
nil,
metadataLoader,
valueLoader,
)
require.NoError(t, err)
assert.Equal(t, []float64{220, 219.5}, value)
}
func TestQueryDataObjectAttributeValuePropagatesRedisTokenNotFound(t *testing.T) {
loader := func(context.Context, constants.DataObjectType, string, string) (string, error) {
return "", fmt.Errorf("%w: token", common.ErrParameterTokenNotFound)
}
_, err := queryDataObjectAttributeValue(
context.Background(),
constants.DataObjectTypeParameter,
"missing-token",
"name",
loader,
nil,
nil,
)
require.Error(t, err)
assert.ErrorIs(t, err, common.ErrParameterTokenNotFound)
assert.True(t, isDataObjectTokenNotFound(err))
}
func TestDecodeParameterHashValue(t *testing.T) {
tests := []struct {
name string
rawValue string
attributeType string
expected any
}{
{name: "boolean", rawValue: "true", attributeType: "BOOLEAN", expected: true},
{name: "integer", rawValue: "42", attributeType: "INTEGER", expected: int64(42)},
{name: "numeric", rawValue: "1234567890.123456789", attributeType: "NUMERIC(30,9)", expected: json.Number("1234567890.123456789")},
{name: "jsonb", rawValue: `{"key":"value"}`, attributeType: "JSONB", expected: map[string]any{"key": "value"}},
{name: "string", rawValue: "cable", attributeType: "CHARACTER VARYING(64)", expected: "cable"},
{name: "null", rawValue: "null", attributeType: "INTEGER", expected: nil},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := decodeParameterHashValue(test.rawValue, test.attributeType)
require.NoError(t, err)
assert.Equal(t, test.expected, actual)
})
}
}
func hashFieldLoaderForTest(fields map[string]string) dataObjectHashFieldLoader {
return func(_ context.Context, _ constants.DataObjectType, _ string, field string) (string, error) {
value, exists := fields[field]
if !exists {
return "", fmt.Errorf("field %q not found", field)
}
return value, nil
}
}