200 lines
5.5 KiB
Go
200 lines
5.5 KiB
Go
|
|
package database
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"regexp"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"modelRT/common"
|
||
|
|
|
||
|
|
"github.com/DATA-DOG/go-sqlmock"
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
"gorm.io/driver/postgres"
|
||
|
|
"gorm.io/gorm"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestBuildMeasurementTokenValidationQuery(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
token string
|
||
|
|
wantArgs []any
|
||
|
|
wantWhere string
|
||
|
|
wantErr bool
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "seven-part token",
|
||
|
|
token: "grid.zone.station.nspath.component.bay.measurement",
|
||
|
|
wantArgs: []any{"grid", "zone", "station", "nspath", "component", "measurement"},
|
||
|
|
wantWhere: "WHERE g.tagname = ?",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "four-part token",
|
||
|
|
token: "nspath.component.bay.measurement",
|
||
|
|
wantArgs: []any{"nspath", "component", "measurement"},
|
||
|
|
wantWhere: "WHERE c.nspath = ?",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "two-part token",
|
||
|
|
token: "nspath.measurement",
|
||
|
|
wantArgs: []any{"nspath", "measurement"},
|
||
|
|
wantWhere: "WHERE c.nspath = ?",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "non-bay group",
|
||
|
|
token: "nspath.component.rated.attribute",
|
||
|
|
wantErr: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "empty segment",
|
||
|
|
token: "nspath..bay.measurement",
|
||
|
|
wantErr: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "invalid segment count",
|
||
|
|
token: "grid.zone.station",
|
||
|
|
wantErr: true,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
query, args, err := buildMeasurementTokenValidationQuery(tt.token)
|
||
|
|
if tt.wantErr {
|
||
|
|
require.Error(t, err)
|
||
|
|
assert.ErrorIs(t, err, common.ErrInvalidMeasurementToken)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Contains(t, query, "INNER JOIN component AS c ON c.global_uuid = m.component_uuid")
|
||
|
|
assert.Contains(t, query, "INNER JOIN bay AS b ON b.bay_uuid = m.bay_uuid")
|
||
|
|
assert.Contains(t, query, tt.wantWhere)
|
||
|
|
assert.NotContains(t, query, "grid_idWHERE")
|
||
|
|
assert.Regexp(t, `grid_id\s+WHERE`, query)
|
||
|
|
assert.NotContains(t, query, "\n")
|
||
|
|
assert.NotContains(t, query, "\t")
|
||
|
|
assert.Equal(t, tt.wantArgs, args)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidateMeasurementToken(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
count int64
|
||
|
|
queryErr error
|
||
|
|
wantErr error
|
||
|
|
}{
|
||
|
|
{name: "exists", count: 1},
|
||
|
|
{name: "not found", count: 0, wantErr: common.ErrMeasurementTokenNotFound},
|
||
|
|
{name: "ambiguous", count: 2, wantErr: common.ErrAmbiguousMeasurementToken},
|
||
|
|
{name: "query failure", queryErr: errors.New("database unavailable")},
|
||
|
|
}
|
||
|
|
|
||
|
|
const token = "nspath.measurement"
|
||
|
|
query, _, err := buildMeasurementTokenValidationQuery(token)
|
||
|
|
require.NoError(t, err)
|
||
|
|
expectedQuery := query
|
||
|
|
for i := 1; strings.Contains(expectedQuery, "?"); i++ {
|
||
|
|
expectedQuery = strings.Replace(expectedQuery, "?", fmt.Sprintf("$%d", i), 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
sqlDB, mock, err := sqlmock.New()
|
||
|
|
require.NoError(t, err)
|
||
|
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||
|
|
|
||
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
expectation := mock.ExpectQuery(regexp.QuoteMeta(expectedQuery)).
|
||
|
|
WithArgs("nspath", "measurement")
|
||
|
|
if tt.queryErr != nil {
|
||
|
|
expectation.WillReturnError(tt.queryErr)
|
||
|
|
} else {
|
||
|
|
expectation.WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tt.count))
|
||
|
|
}
|
||
|
|
|
||
|
|
err = ValidateMeasurementToken(context.Background(), db, token)
|
||
|
|
if tt.wantErr != nil {
|
||
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
||
|
|
} else if tt.queryErr != nil {
|
||
|
|
require.Error(t, err)
|
||
|
|
assert.Contains(t, err.Error(), tt.queryErr.Error())
|
||
|
|
} else {
|
||
|
|
require.NoError(t, err)
|
||
|
|
}
|
||
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildMeasurementRowsQuerySeparatesLimitClause(t *testing.T) {
|
||
|
|
validationQuery, _, err := buildMeasurementTokenValidationQuery("nspath.measurement")
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
query := buildMeasurementRowsQuery(validationQuery)
|
||
|
|
assert.NotContains(t, query, "?LIMIT")
|
||
|
|
assert.Regexp(t, `m\.tag = \?\s+LIMIT 2$`, query)
|
||
|
|
assert.NotContains(t, query, "\n")
|
||
|
|
assert.NotContains(t, query, "\t")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueryMeasurementByDataObjectToken(t *testing.T) {
|
||
|
|
sqlDB, mock, err := sqlmock.New()
|
||
|
|
require.NoError(t, err)
|
||
|
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||
|
|
|
||
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
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",
|
||
|
|
"name",
|
||
|
|
"mode",
|
||
|
|
"size",
|
||
|
|
"data_source",
|
||
|
|
"event_plan",
|
||
|
|
"binding",
|
||
|
|
"component_uuid",
|
||
|
|
}).AddRow(
|
||
|
|
int64(10),
|
||
|
|
"measurement",
|
||
|
|
"A phase current",
|
||
|
|
int16(1),
|
||
|
|
10,
|
||
|
|
`{"type":1,"io_address":{"channel":"tm1"}}`,
|
||
|
|
`{"enabled":true}`,
|
||
|
|
`{"ct":{"ratio":1}}`,
|
||
|
|
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",
|
||
|
|
"grid",
|
||
|
|
"zone",
|
||
|
|
"station",
|
||
|
|
}).AddRow(componentUUID, "nspath", "component", "grid", "zone", "station"))
|
||
|
|
|
||
|
|
measurement, component, err := QueryMeasurementByDataObjectToken(context.Background(), db, "nspath.measurement")
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Equal(t, int64(10), measurement.ID)
|
||
|
|
assert.Equal(t, int16(1), measurement.Mode)
|
||
|
|
assert.Equal(t, float64(1), measurement.DataSource["type"])
|
||
|
|
assert.Equal(t, "grid", component.GridName)
|
||
|
|
assert.Equal(t, "component", component.Tag)
|
||
|
|
require.NoError(t, mock.ExpectationsWereMet())
|
||
|
|
}
|