274 lines
8.6 KiB
Go
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
|
|
}
|
|
}
|