266 lines
9.2 KiB
Go
266 lines
9.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"modelRT/common/errcode"
|
|
"modelRT/constants"
|
|
"modelRT/orm"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestValidateDataObjectAttributeUpdateParameterGroups(t *testing.T) {
|
|
groups := []string{
|
|
"rated",
|
|
"setup",
|
|
"model",
|
|
"stable",
|
|
"craft",
|
|
"integrity",
|
|
"behavior",
|
|
}
|
|
|
|
for _, group := range groups {
|
|
t.Run(group, func(t *testing.T) {
|
|
request := dataObjectAttributeUpdateRequest{
|
|
Token: fmt.Sprintf("nspath.component.%s.attribute", group),
|
|
Field: "VALUE",
|
|
Value: json.RawMessage(`"15.2"`),
|
|
}
|
|
|
|
dataObjectType, field, value, err := validateDataObjectAttributeUpdate(request)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, constants.DataObjectTypeParameter, dataObjectType)
|
|
assert.Equal(t, "value", field)
|
|
assert.Equal(t, "15.2", value)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateDataObjectAttributeUpdateRejectsUnsupportedParameterGroups(t *testing.T) {
|
|
for _, group := range []string{"component", "base_extend"} {
|
|
t.Run(group, func(t *testing.T) {
|
|
_, _, _, err := validateDataObjectAttributeUpdate(dataObjectAttributeUpdateRequest{
|
|
Token: fmt.Sprintf("nspath.component.%s.attribute", group),
|
|
Field: "value",
|
|
Value: json.RawMessage(`"uuid"`),
|
|
})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "do not support token6="+group)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateDataObjectAttributeUpdateMeasurementFields(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
field string
|
|
value string
|
|
expected any
|
|
wantError bool
|
|
}{
|
|
{name: "numeric value", field: "value", value: `15.2`, expected: float64(15.2)},
|
|
{name: "numeric string value", field: "value", value: `"15.2"`, expected: float64(15.2)},
|
|
{name: "boolean automatic mode", field: "mode", value: `true`, expected: true},
|
|
{name: "string manual mode", field: "mode", value: `"false"`, expected: false},
|
|
{name: "invalid mode", field: "mode", value: `"manual"`, wantError: true},
|
|
{name: "unsupported field", field: "name", value: `"measurement"`, wantError: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dataObjectType, field, value, err := validateDataObjectAttributeUpdate(dataObjectAttributeUpdateRequest{
|
|
Token: "nspath.component.bay.measurement",
|
|
Field: tt.field,
|
|
Value: json.RawMessage(tt.value),
|
|
})
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assert.Equal(t, constants.DataObjectTypeMeasurement, dataObjectType)
|
|
assert.Equal(t, tt.field, field)
|
|
assert.Equal(t, tt.expected, value)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateDataObjectAttributeUpdateRejectsShortMeasurementToken(t *testing.T) {
|
|
_, _, _, err := validateDataObjectAttributeUpdate(dataObjectAttributeUpdateRequest{
|
|
Token: "nspath.measurement",
|
|
Field: "value",
|
|
Value: json.RawMessage(`1`),
|
|
})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "token6=bay")
|
|
}
|
|
|
|
func TestValidateDataObjectAttributeUpdateRequiredFields(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
request dataObjectAttributeUpdateRequest
|
|
}{
|
|
{name: "missing token", request: dataObjectAttributeUpdateRequest{Field: "value", Value: json.RawMessage(`1`)}},
|
|
{name: "missing field", request: dataObjectAttributeUpdateRequest{Token: "nspath.measurement", Value: json.RawMessage(`1`)}},
|
|
{name: "missing value", request: dataObjectAttributeUpdateRequest{Token: "nspath.measurement", Field: "value"}},
|
|
{name: "null value", request: dataObjectAttributeUpdateRequest{Token: "nspath.measurement", Field: "value", Value: json.RawMessage(`null`)}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, _, _, err := validateDataObjectAttributeUpdate(tt.request)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateMeasurementDataObjectLocksRowAndUpdatesMode(t *testing.T) {
|
|
db, mock, closeDB := newDataObjectUpdateTestDB(t)
|
|
defer closeDB()
|
|
|
|
mock.ExpectBegin()
|
|
tx := db.Begin()
|
|
require.NoError(t, tx.Error)
|
|
expectMeasurementResolution(mock, 0)
|
|
mock.ExpectExec(`UPDATE "measurement" SET .*"mode"=\$1.*"operations"=.*WHERE id = \$5`).
|
|
WithArgs(int16(1), 500, 500, sqlmock.AnyArg(), int64(10)).
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
mock.ExpectRollback()
|
|
|
|
result, err := updateMeasurementDataObject(context.Background(), tx, "nspath.measurement", "mode", true, nil, nil)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, result.message, "automatic")
|
|
require.NoError(t, tx.Rollback().Error)
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestUpdateMeasurementDataObjectReturnsMessageWhenModeIsUnchanged(t *testing.T) {
|
|
db, mock, closeDB := newDataObjectUpdateTestDB(t)
|
|
defer closeDB()
|
|
|
|
mock.ExpectBegin()
|
|
tx := db.Begin()
|
|
require.NoError(t, tx.Error)
|
|
expectMeasurementResolution(mock, 1)
|
|
mock.ExpectRollback()
|
|
|
|
result, err := updateMeasurementDataObject(context.Background(), tx, "nspath.measurement", "mode", true, nil, nil)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, result.message, "already")
|
|
require.NoError(t, tx.Rollback().Error)
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestUpdateMeasurementDataObjectRejectsValueInAutomaticMode(t *testing.T) {
|
|
db, mock, closeDB := newDataObjectUpdateTestDB(t)
|
|
defer closeDB()
|
|
|
|
mock.ExpectBegin()
|
|
tx := db.Begin()
|
|
require.NoError(t, tx.Error)
|
|
expectMeasurementResolution(mock, 1)
|
|
mock.ExpectRollback()
|
|
|
|
_, err := updateMeasurementDataObject(context.Background(), tx, "nspath.measurement", "value", float64(15.2), nil, nil)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "read-only")
|
|
require.NoError(t, tx.Rollback().Error)
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestUpdateMeasurementDataObjectWritesValueInManualMode(t *testing.T) {
|
|
db, mock, closeDB := newDataObjectUpdateTestDB(t)
|
|
defer closeDB()
|
|
|
|
mock.ExpectBegin()
|
|
tx := db.Begin()
|
|
require.NoError(t, tx.Error)
|
|
expectMeasurementResolution(mock, 0)
|
|
mock.ExpectExec(`UPDATE "measurement" SET "operations"=.*WHERE id = \$4`).
|
|
WithArgs(500, 500, sqlmock.AnyArg(), int64(10)).
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
mock.ExpectRollback()
|
|
|
|
called := false
|
|
writer := func(_ context.Context, measurement *orm.Measurement, value float64) error {
|
|
called = true
|
|
assert.Equal(t, int64(10), measurement.ID)
|
|
assert.Equal(t, float64(15.2), value)
|
|
return nil
|
|
}
|
|
dataRTCalled := false
|
|
dataRTWriter := func(_ context.Context, dataSource orm.JSONMap, value float64) error {
|
|
dataRTCalled = true
|
|
assert.Equal(t, float64(15.2), value)
|
|
assert.Equal(t, float64(1), dataSource["type"])
|
|
return nil
|
|
}
|
|
result, err := updateMeasurementDataObject(context.Background(), tx, "nspath.measurement", "value", float64(15.2), writer, dataRTWriter)
|
|
require.NoError(t, err)
|
|
assert.True(t, called)
|
|
assert.True(t, dataRTCalled)
|
|
assert.Contains(t, result.message, "updated")
|
|
assert.False(t, result.recordFailure)
|
|
require.NoError(t, tx.Rollback().Error)
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestUpdateMeasurementDataObjectReturnsFailureResultAndAppError(t *testing.T) {
|
|
db, mock, closeDB := newDataObjectUpdateTestDB(t)
|
|
defer closeDB()
|
|
|
|
mock.ExpectBegin()
|
|
tx := db.Begin()
|
|
require.NoError(t, tx.Error)
|
|
expectMeasurementResolution(mock, 0)
|
|
mock.ExpectRollback()
|
|
|
|
writeErr := fmt.Errorf("write value failed")
|
|
writer := func(context.Context, *orm.Measurement, float64) error { return writeErr }
|
|
result, err := updateMeasurementDataObject(context.Background(), tx, "nspath.measurement", "value", float64(15.2), writer, nil)
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, errcode.ErrMeasurementValueUpdateFailed)
|
|
assert.ErrorIs(t, err, writeErr)
|
|
assert.True(t, result.recordFailure)
|
|
assert.Equal(t, int64(10), result.measurementID)
|
|
assert.Equal(t, float64(15.2), result.value)
|
|
require.NoError(t, tx.Rollback().Error)
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func newDataObjectUpdateTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
|
|
t.Helper()
|
|
sqlDB, mock, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
|
|
require.NoError(t, err)
|
|
return db, mock, func() { _ = sqlDB.Close() }
|
|
}
|
|
|
|
func expectMeasurementResolution(mock sqlmock.Sqlmock, mode int16) {
|
|
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
|
|
mock.ExpectQuery(`(?s)SELECT m\.\*.*WHERE c\.nspath = \$1.*AND m\.tag = \$2.*LIMIT 2`).
|
|
WithArgs("nspath", "measurement").
|
|
WillReturnRows(sqlmock.NewRows([]string{
|
|
"id", "tag", "mode", "data_source", "component_uuid",
|
|
}).AddRow(int64(10), "measurement", mode, `{"type":1,"io_address":{"station":"station","device":"device","channel":"tm1"}}`, componentUUID))
|
|
mock.ExpectQuery(`(?s)SELECT global_uuid, nspath, tag, grid, zone, station.*WHERE global_uuid = \$1.*LIMIT 1`).
|
|
WithArgs(componentUUID).
|
|
WillReturnRows(sqlmock.NewRows([]string{"global_uuid", "nspath", "tag"}).
|
|
AddRow(componentUUID, "nspath", "component"))
|
|
mock.ExpectQuery(`(?s)SELECT \* FROM "measurement" WHERE id = \$1.*LIMIT \$2 FOR UPDATE`).
|
|
WithArgs(int64(10), 1).
|
|
WillReturnRows(sqlmock.NewRows([]string{
|
|
"id", "tag", "mode", "data_source", "component_uuid",
|
|
}).AddRow(int64(10), "measurement", mode, `{"type":1,"io_address":{"station":"station","device":"device","channel":"tm1"}}`, componentUUID))
|
|
}
|