modelRT/database/query_component_measurement...

200 lines
5.5 KiB
Go
Raw Normal View History

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())
}