241 lines
7.4 KiB
Go
241 lines
7.4 KiB
Go
package manualsync
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"modelRT/config"
|
|
"modelRT/constants"
|
|
"modelRT/orm"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type capturedRequest struct {
|
|
Path string
|
|
Method string
|
|
ContentType string
|
|
Payload Request
|
|
}
|
|
|
|
func TestClientSyncRoutesAndMapsDataSources(t *testing.T) {
|
|
cl3611Requests := make(chan capturedRequest, 2)
|
|
protocol104Requests := make(chan capturedRequest, 1)
|
|
client, err := NewClient(config.ManualSyncConfig{
|
|
ProtocolCL3611URL: "http://cl3611.test",
|
|
Protocol104URL: "http://protocol104.test",
|
|
APIPath: "/api/manual",
|
|
Timeout: time.Second,
|
|
})
|
|
require.NoError(t, err)
|
|
client.httpClient.Transport = roundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
var payload Request
|
|
require.NoError(t, json.NewDecoder(request.Body).Decode(&payload))
|
|
captured := capturedRequest{
|
|
Path: request.URL.Path,
|
|
Method: request.Method,
|
|
ContentType: request.Header.Get("Content-Type"),
|
|
Payload: payload,
|
|
}
|
|
if request.URL.Host == "cl3611.test" {
|
|
cl3611Requests <- captured
|
|
} else {
|
|
protocol104Requests <- captured
|
|
}
|
|
return httpResponse(http.StatusNoContent, ""), nil
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
dataSource orm.JSONMap
|
|
requests <-chan capturedRequest
|
|
wantTarget Target
|
|
}{
|
|
{
|
|
name: "CL3611 phasor",
|
|
dataSource: orm.JSONMap{
|
|
"type": 1,
|
|
"io_address": map[string]any{
|
|
"dtype": 1, "station": "001", "device": "ssu001",
|
|
"channel": "TM1", "option": "RMS",
|
|
},
|
|
},
|
|
requests: cl3611Requests,
|
|
wantTarget: Target{
|
|
Type: 1, Station: "001", MainPos: "ssu001", SubPos: "TM1", Option: "RMS",
|
|
},
|
|
},
|
|
{
|
|
name: "CL3611 sample",
|
|
dataSource: orm.JSONMap{
|
|
"type": 1,
|
|
"io_address": map[string]any{
|
|
"dtype": 2, "station": "002", "device": "ssu002",
|
|
"channel": "TS01", "option": "ignored",
|
|
},
|
|
},
|
|
requests: cl3611Requests,
|
|
wantTarget: Target{
|
|
Type: 2, Station: "002", MainPos: "ssu002", SubPos: "TS01", Option: "",
|
|
},
|
|
},
|
|
{
|
|
name: "104",
|
|
dataSource: orm.JSONMap{
|
|
"type": 2,
|
|
"io_address": map[string]any{
|
|
"station": "station000", "packet": 10, "offset": 35,
|
|
},
|
|
},
|
|
requests: protocol104Requests,
|
|
wantTarget: Target{
|
|
Type: 3, Station: "station000", MainPos: "10", SubPos: "35", Option: "",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := client.Sync(context.Background(), test.dataSource, constants.MeasurementModeAutomatic, nil)
|
|
require.NoError(t, err)
|
|
captured := <-test.requests
|
|
assert.Equal(t, http.MethodPost, captured.Method)
|
|
assert.Equal(t, "/api/manual", captured.Path)
|
|
assert.Equal(t, "application/json", captured.ContentType)
|
|
assert.Equal(t, constants.MeasurementModeAutomatic, captured.Payload.Mode)
|
|
assert.Nil(t, captured.Payload.Data)
|
|
assert.Equal(t, test.wantTarget, captured.Payload.Target)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientSyncIncludesManualValueData(t *testing.T) {
|
|
requests := make(chan capturedRequest, 1)
|
|
client, err := NewClient(config.ManualSyncConfig{
|
|
ProtocolCL3611URL: "http://cl3611.test",
|
|
Protocol104URL: "http://protocol104.test",
|
|
APIPath: "api/manual",
|
|
Timeout: time.Second,
|
|
})
|
|
require.NoError(t, err)
|
|
client.httpClient.Transport = captureTransport(t, requests)
|
|
|
|
sample := SyntheticData{Time: 1736305467506000000, Value: 1.25}
|
|
err = client.Sync(context.Background(), orm.JSONMap{
|
|
"type": 2,
|
|
"io_address": map[string]any{
|
|
"station": "station000", "packet": 1, "offset": 2,
|
|
},
|
|
}, constants.MeasurementModeManual, &sample)
|
|
require.NoError(t, err)
|
|
captured := <-requests
|
|
assert.Equal(t, constants.MeasurementModeManual, captured.Payload.Mode)
|
|
assert.Equal(t, []SyntheticData{sample}, captured.Payload.Data)
|
|
}
|
|
|
|
func TestClientSyncRejectsInvalidDataSources(t *testing.T) {
|
|
client, err := NewClient(config.ManualSyncConfig{
|
|
ProtocolCL3611URL: "http://cl3611.test",
|
|
Protocol104URL: "http://protocol104.test",
|
|
APIPath: "/api/manual",
|
|
Timeout: time.Second,
|
|
})
|
|
require.NoError(t, err)
|
|
client.httpClient.Transport = roundTripFunc(func(*http.Request) (*http.Response, error) {
|
|
t.Fatal("invalid data source must not call endpoint")
|
|
return nil, nil
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
dataSource orm.JSONMap
|
|
wantError string
|
|
}{
|
|
{name: "unsupported type", dataSource: orm.JSONMap{"type": 3, "io_address": map[string]any{"station": "s"}}, wantError: "unsupported"},
|
|
{name: "invalid dtype", dataSource: orm.JSONMap{"type": 1, "io_address": map[string]any{"dtype": 3, "station": "s", "device": "d", "channel": "c"}}, wantError: "dtype"},
|
|
{name: "missing station", dataSource: orm.JSONMap{"type": 2, "io_address": map[string]any{"packet": 1, "offset": 2}}, wantError: "station"},
|
|
{name: "fractional packet", dataSource: orm.JSONMap{"type": 2, "io_address": map[string]any{"station": "s", "packet": 1.5, "offset": 2}}, wantError: "packet"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := client.Sync(context.Background(), test.dataSource, constants.MeasurementModeManual, nil)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), test.wantError)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientSyncReturnsNonSuccessResponse(t *testing.T) {
|
|
client, err := NewClient(config.ManualSyncConfig{
|
|
ProtocolCL3611URL: "http://cl3611.test",
|
|
Protocol104URL: "http://protocol104.test",
|
|
APIPath: "/api/manual",
|
|
Timeout: time.Second,
|
|
})
|
|
require.NoError(t, err)
|
|
client.httpClient.Transport = roundTripFunc(func(*http.Request) (*http.Response, error) {
|
|
return httpResponse(http.StatusServiceUnavailable, "downstream unavailable"), nil
|
|
})
|
|
|
|
err = client.Sync(context.Background(), orm.JSONMap{
|
|
"type": 2,
|
|
"io_address": map[string]any{
|
|
"station": "s", "packet": 1, "offset": 2,
|
|
},
|
|
}, constants.MeasurementModeManual, nil)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "Service Unavailable")
|
|
assert.Contains(t, err.Error(), "downstream unavailable")
|
|
}
|
|
|
|
func TestNewClientValidatesConfiguration(t *testing.T) {
|
|
_, err := NewClient(config.ManualSyncConfig{})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "timeout")
|
|
|
|
_, err = NewClient(config.ManualSyncConfig{
|
|
ProtocolCL3611URL: "127.0.0.1:9001",
|
|
Protocol104URL: "http://127.0.0.1:9002",
|
|
APIPath: "/api/manual",
|
|
Timeout: time.Second,
|
|
})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid protocol CL3611 URL")
|
|
}
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (roundTrip roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
return roundTrip(request)
|
|
}
|
|
|
|
func captureTransport(t *testing.T, requests chan<- capturedRequest) http.RoundTripper {
|
|
t.Helper()
|
|
return roundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
var payload Request
|
|
require.NoError(t, json.NewDecoder(request.Body).Decode(&payload))
|
|
requests <- capturedRequest{
|
|
Path: request.URL.Path,
|
|
Method: request.Method,
|
|
ContentType: request.Header.Get("Content-Type"),
|
|
Payload: payload,
|
|
}
|
|
return httpResponse(http.StatusNoContent, ""), nil
|
|
})
|
|
}
|
|
|
|
func httpResponse(status int, body string) *http.Response {
|
|
return &http.Response{
|
|
StatusCode: status,
|
|
Status: http.StatusText(status),
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
Header: make(http.Header),
|
|
}
|
|
}
|