2026-07-15 17:33:55 +08:00
package handler
import (
"context"
2026-07-30 15:32:22 +08:00
"encoding/json"
"fmt"
2026-07-17 16:40:34 +08:00
"net/http"
"net/http/httptest"
2026-07-15 17:33:55 +08:00
"testing"
"modelRT/common"
2026-07-16 16:04:39 +08:00
"modelRT/constants"
2026-07-15 17:33:55 +08:00
"modelRT/orm"
2026-07-17 16:40:34 +08:00
"github.com/gin-gonic/gin"
2026-07-15 17:33:55 +08:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
2026-07-17 16:40:34 +08:00
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 )
} )
}
}
2026-07-16 16:04:39 +08:00
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" } ,
2026-07-16 16:59:11 +08:00
{ 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 } ,
2026-07-16 16:04:39 +08:00
{ 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 ) {
2026-07-20 11:32:40 +08:00
err := validateDataObjectField ( tt . dataObjectType , tt . field )
2026-07-16 16:04:39 +08:00
if tt . wantErr == nil {
require . NoError ( t , err )
return
}
require . Error ( t , err )
if tt . wantErr != assert . AnError {
assert . ErrorIs ( t , err , tt . wantErr )
}
} )
}
}
2026-07-30 15:32:22 +08:00
func TestQueryParameterAttributeValueFromRedisHash ( t * testing . T ) {
fields := map [ string ] string {
"value" : "220.50" ,
"type" : "DOUBLE PRECISION" ,
"name" : "110kV_TV.cable_22.rated.rated_voltage" ,
"description" : "额定电压" ,
2026-07-16 16:04:39 +08:00
}
2026-07-30 15:32:22 +08:00
loader := hashFieldLoaderForTest ( fields )
2026-07-16 16:04:39 +08:00
2026-07-30 15:32:22 +08:00
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 )
2026-07-16 16:04:39 +08:00
2026-07-30 15:32:22 +08:00
description , err := queryDataObjectAttributeValue (
context . Background ( ) ,
constants . DataObjectTypeParameter ,
"parameter-token" ,
"description" ,
loader ,
nil ,
nil ,
)
require . NoError ( t , err )
assert . Equal ( t , "额定电压" , description )
2026-07-16 16:04:39 +08:00
}
2026-07-30 15:32:22 +08:00
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 (
2026-07-16 16:04:39 +08:00
context . Background ( ) ,
2026-07-30 15:32:22 +08:00
constants . DataObjectTypeMeasurement ,
"measurement-token" ,
"mode" ,
loader ,
2026-07-16 16:04:39 +08:00
nil ,
nil ,
)
2026-07-30 15:32:22 +08:00
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 )
2026-07-16 16:04:39 +08:00
}
2026-07-30 15:32:22 +08:00
func TestQueryMeasurementRealtimeValueUsesDataSourceFromRedisHash ( t * testing . T ) {
2026-07-15 17:33:55 +08:00
dataSource := orm . JSONMap {
"type" : float64 ( 1 ) ,
"io_address" : map [ string ] any {
2026-07-30 15:32:22 +08:00
"station" : "001" ,
2026-07-15 17:33:55 +08:00
"channel" : "tm1p" ,
} ,
}
2026-07-30 15:32:22 +08:00
metadataLoader := func ( _ context . Context , token string ) ( orm . JSONMap , int , error ) {
assert . Equal ( t , "measurement-token" , token )
return dataSource , 2 , nil
2026-07-15 17:33:55 +08:00
}
2026-07-30 15:32:22 +08:00
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
2026-07-15 17:33:55 +08:00
}
2026-07-30 15:32:22 +08:00
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 )
}
2026-07-15 17:33:55 +08:00
2026-07-30 15:32:22 +08:00
func TestQueryDataObjectAttributeValuePropagatesRedisTokenNotFound ( t * testing . T ) {
loader := func ( context . Context , constants . DataObjectType , string , string ) ( string , error ) {
return "" , fmt . Errorf ( "%w: token" , common . ErrParameterTokenNotFound )
2026-07-15 17:33:55 +08:00
}
2026-07-30 15:32:22 +08:00
_ , err := queryDataObjectAttributeValue (
2026-07-15 17:33:55 +08:00
context . Background ( ) ,
2026-07-30 15:32:22 +08:00
constants . DataObjectTypeParameter ,
"missing-token" ,
"name" ,
loader ,
nil ,
2026-07-15 17:33:55 +08:00
nil ,
)
require . Error ( t , err )
2026-07-30 15:32:22 +08:00
assert . ErrorIs ( t , err , common . ErrParameterTokenNotFound )
assert . True ( t , isDataObjectTokenNotFound ( err ) )
2026-07-15 17:33:55 +08:00
}
2026-07-30 15:32:22 +08:00
func TestDecodeParameterHashValue ( t * testing . T ) {
2026-07-17 16:40:34 +08:00
tests := [ ] struct {
2026-07-30 15:32:22 +08:00
name string
rawValue string
attributeType string
expected any
2026-07-17 16:40:34 +08:00
} {
2026-07-30 15:32:22 +08:00
{ 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 } ,
2026-07-17 16:40:34 +08:00
}
2026-07-15 17:33:55 +08:00
2026-07-30 15:32:22 +08:00
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
actual , err := decodeParameterHashValue ( test . rawValue , test . attributeType )
2026-07-17 16:40:34 +08:00
require . NoError ( t , err )
2026-07-30 15:32:22 +08:00
assert . Equal ( t , test . expected , actual )
2026-07-17 16:40:34 +08:00
} )
}
2026-07-15 17:33:55 +08:00
}
2026-07-30 15:32:22 +08:00
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
}
}