211 lines
4.8 KiB
Go
211 lines
4.8 KiB
Go
package stub_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/uopi/uopi/internal/datasource"
|
|
"github.com/uopi/uopi/internal/datasource/stub"
|
|
)
|
|
|
|
func newStub(t *testing.T) *stub.Stub {
|
|
t.Helper()
|
|
s := stub.New()
|
|
if err := s.Connect(context.Background()); err != nil {
|
|
t.Fatalf("Connect: %v", err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func TestName(t *testing.T) {
|
|
if stub.New().Name() != "stub" {
|
|
t.Error("Name() should return 'stub'")
|
|
}
|
|
}
|
|
|
|
func TestListSignals(t *testing.T) {
|
|
s := newStub(t)
|
|
sigs, err := s.ListSignals(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("ListSignals: %v", err)
|
|
}
|
|
if len(sigs) == 0 {
|
|
t.Fatal("expected at least one signal")
|
|
}
|
|
names := make(map[string]bool)
|
|
for _, m := range sigs {
|
|
names[m.Name] = true
|
|
}
|
|
for _, want := range []string{"sine_1hz", "ramp_10s", "toggle_1hz", "noise", "setpoint"} {
|
|
if !names[want] {
|
|
t.Errorf("signal %q not in ListSignals", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetMetadata(t *testing.T) {
|
|
s := newStub(t)
|
|
m, err := s.GetMetadata(context.Background(), "sine_1hz")
|
|
if err != nil {
|
|
t.Fatalf("GetMetadata: %v", err)
|
|
}
|
|
if m.Type != datasource.TypeFloat64 {
|
|
t.Errorf("Type = %v, want TypeFloat64", m.Type)
|
|
}
|
|
if m.Unit == "" {
|
|
t.Error("expected non-empty Unit")
|
|
}
|
|
}
|
|
|
|
func TestGetMetadataNotFound(t *testing.T) {
|
|
s := newStub(t)
|
|
_, err := s.GetMetadata(context.Background(), "no:such:pv")
|
|
if err != datasource.ErrNotFound {
|
|
t.Errorf("want ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSubscribeReceivesValues(t *testing.T) {
|
|
s := newStub(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
ch := make(chan datasource.Value, 4)
|
|
stop, err := s.Subscribe(ctx, "sine_1hz", ch)
|
|
if err != nil {
|
|
t.Fatalf("Subscribe: %v", err)
|
|
}
|
|
defer stop()
|
|
|
|
select {
|
|
case v := <-ch:
|
|
if v.Quality != datasource.QualityGood {
|
|
t.Errorf("Quality = %v, want Good", v.Quality)
|
|
}
|
|
if _, ok := v.Data.(float64); !ok {
|
|
t.Errorf("Data type = %T, want float64", v.Data)
|
|
}
|
|
if v.Timestamp.IsZero() {
|
|
t.Error("Timestamp is zero")
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("timeout waiting for first value")
|
|
}
|
|
}
|
|
|
|
func TestSubscribeUnknown(t *testing.T) {
|
|
s := newStub(t)
|
|
_, err := s.Subscribe(context.Background(), "no:such", make(chan datasource.Value, 1))
|
|
if err != datasource.ErrNotFound {
|
|
t.Errorf("want ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSubscribeCancel(t *testing.T) {
|
|
s := newStub(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
ch := make(chan datasource.Value, 4)
|
|
stop, err := s.Subscribe(ctx, "ramp_10s", ch)
|
|
if err != nil {
|
|
t.Fatalf("Subscribe: %v", err)
|
|
}
|
|
|
|
// Drain one value to confirm it's running.
|
|
select {
|
|
case <-ch:
|
|
case <-ctx.Done():
|
|
t.Fatal("timeout waiting for first value")
|
|
}
|
|
|
|
stop() // cancel subscription
|
|
|
|
// Drain remaining buffered values then confirm channel goes quiet.
|
|
time.Sleep(200 * time.Millisecond)
|
|
for len(ch) > 0 {
|
|
<-ch
|
|
}
|
|
time.Sleep(300 * time.Millisecond)
|
|
if len(ch) > 0 {
|
|
t.Error("channel still receiving after stop()")
|
|
}
|
|
}
|
|
|
|
func TestWriteSetpoint(t *testing.T) {
|
|
s := newStub(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
if err := s.Write(context.Background(), "setpoint", float64(99.0)); err != nil {
|
|
t.Fatalf("Write: %v", err)
|
|
}
|
|
|
|
ch := make(chan datasource.Value, 4)
|
|
stop, err := s.Subscribe(ctx, "setpoint", ch)
|
|
if err != nil {
|
|
t.Fatalf("Subscribe: %v", err)
|
|
}
|
|
defer stop()
|
|
|
|
select {
|
|
case v := <-ch:
|
|
f, ok := v.Data.(float64)
|
|
if !ok {
|
|
t.Fatalf("Data type = %T, want float64", v.Data)
|
|
}
|
|
if f != 99.0 {
|
|
t.Errorf("setpoint value = %g, want 99.0", f)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("timeout waiting for setpoint value")
|
|
}
|
|
}
|
|
|
|
func TestWriteReadOnly(t *testing.T) {
|
|
s := newStub(t)
|
|
err := s.Write(context.Background(), "sine_1hz", float64(0))
|
|
if err != datasource.ErrNotWritable {
|
|
t.Errorf("want ErrNotWritable, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteNotFound(t *testing.T) {
|
|
s := newStub(t)
|
|
err := s.Write(context.Background(), "no:such", float64(0))
|
|
if err != datasource.ErrNotFound {
|
|
t.Errorf("want ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHistory(t *testing.T) {
|
|
s := newStub(t)
|
|
_, err := s.History(context.Background(), "sine_1hz", time.Now().Add(-time.Hour), time.Now(), 100)
|
|
if err != datasource.ErrHistoryUnavailable {
|
|
t.Errorf("want ErrHistoryUnavailable, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestToggleBool(t *testing.T) {
|
|
s := newStub(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
ch := make(chan datasource.Value, 4)
|
|
stop, err := s.Subscribe(ctx, "toggle_1hz", ch)
|
|
if err != nil {
|
|
t.Fatalf("Subscribe: %v", err)
|
|
}
|
|
defer stop()
|
|
|
|
select {
|
|
case v := <-ch:
|
|
if _, ok := v.Data.(bool); !ok {
|
|
t.Errorf("toggle_1hz Data type = %T, want bool", v.Data)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("timeout")
|
|
}
|
|
}
|