diff --git a/handler/data_object_attribute_query.go b/handler/data_object_attribute_query.go index 2c5fe1d..bf358ab 100644 --- a/handler/data_object_attribute_query.go +++ b/handler/data_object_attribute_query.go @@ -24,16 +24,9 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { ctx := c.Request.Context() pgClient := database.GetPostgresDBClient() - token := c.Param("token") - field := c.Param("field") - if token == "" { - err := fmt.Errorf("tokens is missing from the path") - logger.Error(ctx, "query tokens from path failed", "error", err, "url", c.Request.RequestURI) - renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) - return - } else if field == "" { - err := fmt.Errorf("field is missing from the path") - logger.Error(ctx, "query field from path failed", "error", err, "url", c.Request.RequestURI) + token, field, err := parseDataObjectAttributeQuery(c) + if err != nil { + logger.Error(ctx, "query token from query parameters failed", "error", err, "url", c.Request.RequestURI) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return } @@ -45,8 +38,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { return } - normalizedField := strings.ToLower(field) - if err := validateDataObjectField(token, dataObjectType, normalizedField); err != nil { + if err := validateDataObjectField(token, dataObjectType, field); err != nil { logger.Warn(ctx, "validate data object field failed", "token", token, "field", field, "error", err) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return @@ -94,7 +86,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { case constants.DataObjectTypeParameter: value, err := buildParameterAttributeValue( ctx, - normalizedField, + field, parameter, func(ctx context.Context, parameter *database.ParameterDataObject) (any, error) { return database.QueryParameterDataObjectValue(ctx, pgClient, parameter) @@ -117,7 +109,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { result := dataObjectAttributeQueryResult{ Token: token, - Field: normalizedField, + Field: field, Code: errcode.ErrProcessSuccess.Code(), Msg: errcode.ErrProcessSuccess.Msg(), Value: value, @@ -128,7 +120,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { case constants.DataObjectTypeMeasurement: value, err := buildMeasurementAttributeValue( ctx, - normalizedField, + field, measurement, measurementComponent, queryMeasurementRealtimeValue, @@ -147,7 +139,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { result := dataObjectAttributeQueryResult{ Token: token, - Field: normalizedField, + Field: field, Code: errcode.ErrProcessSuccess.Code(), Msg: errcode.ErrProcessSuccess.Msg(), Value: value, @@ -160,6 +152,19 @@ func DataObjectAttributeQueryHandler(c *gin.Context) { } } +func parseDataObjectAttributeQuery(c *gin.Context) (string, string, error) { + token := c.Query("token") + if token == "" { + return "", "", fmt.Errorf("token is missing from query parameters") + } + + field := strings.ToLower(c.Query("field")) + if field == "" { + field = "value" + } + return token, field, nil +} + type measurementValueLoader func(context.Context, orm.JSONMap) (any, error) type parameterValueLoader func(context.Context, *database.ParameterDataObject) (any, error) @@ -285,15 +290,7 @@ func buildMeasurementAttributeValue( } return loadValue(ctx, measurement.DataSource) case "mode": - fmt.Println("measurement.Mode:", measurement.Mode) - switch measurement.Mode { - case 0: - return false, nil - case 1: - return true, nil - default: - return nil, fmt.Errorf("invalid measurement mode %d: expected 0 or 1", measurement.Mode) - } + return measurement.Mode == 1, nil case "meta": return "MEASUREMENT", nil case "type": diff --git a/handler/data_object_attribute_query_test.go b/handler/data_object_attribute_query_test.go index 3e2813a..995eb73 100644 --- a/handler/data_object_attribute_query_test.go +++ b/handler/data_object_attribute_query_test.go @@ -2,6 +2,8 @@ package handler import ( "context" + "net/http" + "net/http/httptest" "testing" "modelRT/common" @@ -9,10 +11,75 @@ import ( "modelRT/database" "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 @@ -182,34 +249,28 @@ func TestBuildMeasurementAttributeValueRejectsUnsupportedField(t *testing.T) { func TestBuildMeasurementAttributeValueMode(t *testing.T) { component := &orm.Component{} + tests := []struct { + name string + mode int16 + expected bool + }{ + {name: "automatic mode", mode: 1, expected: true}, + {name: "manual mode", mode: 0, expected: false}, + {name: "other positive mode", mode: 2, expected: false}, + {name: "negative mode", mode: -1, expected: false}, + } - manualMode, err := buildMeasurementAttributeValue( - context.Background(), - "mode", - &orm.Measurement{Mode: 0}, - component, - nil, - ) - require.NoError(t, err) - assert.Equal(t, false, manualMode) - - automaticMode, err := buildMeasurementAttributeValue( - context.Background(), - "mode", - &orm.Measurement{Mode: 1}, - component, - nil, - ) - require.NoError(t, err) - assert.Equal(t, true, automaticMode) - - _, err = buildMeasurementAttributeValue( - context.Background(), - "mode", - &orm.Measurement{Mode: 2}, - component, - nil, - ) - require.Error(t, err) - assert.Contains(t, err.Error(), "expected 0 or 1") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual, err := buildMeasurementAttributeValue( + context.Background(), + "mode", + &orm.Measurement{Mode: tt.mode}, + component, + nil, + ) + require.NoError(t, err) + assert.Equal(t, tt.expected, actual) + }) + } } diff --git a/router/data-object.go b/router/data-object.go index b4850cb..f90d8ba 100644 --- a/router/data-object.go +++ b/router/data-object.go @@ -11,6 +11,6 @@ import ( func registerDataObjectRoutes(rg *gin.RouterGroup, middlewares ...gin.HandlerFunc) { g := rg.Group("/data-object/") g.Use(middlewares...) - g.GET("attribute/:token/:field", handler.DataObjectAttributeQueryHandler) - g.POST("update", handler.DataObjectAttributeUpdateHandler) + g.GET("attribute", handler.DataObjectAttributeQueryHandler) + g.PATCH("attribute", handler.DataObjectAttributeUpdateHandler) }