refactor: use query parameters for data-object attribute APIs
- read token and field from query parameters - default missing or empty fields to value - expose attribute query and update through GET and PATCH routes - return true only for measurement mode 1 - add query parsing and measurement mode test coverage
This commit is contained in:
parent
38f21f3cda
commit
ab029e292b
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue