653 lines
16 KiB
Go
653 lines
16 KiB
Go
package sensors
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func Test_SensorValidate(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
given Sensor
|
|
expected Sensor
|
|
expecErr bool
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
name: "success with all fields",
|
|
given: Sensor{
|
|
SensorID: "temp-001",
|
|
SensorType: "temperature",
|
|
SamplingInterval: ptr(time.Hour * 24),
|
|
ThresholdAbove: ptr(50.0),
|
|
ThresholdBelow: ptr(-10.0),
|
|
},
|
|
expected: Sensor{
|
|
SensorID: "temp-001",
|
|
SensorType: "temperature",
|
|
SamplingInterval: ptr(time.Hour * 24),
|
|
ThresholdAbove: ptr(50.0),
|
|
ThresholdBelow: ptr(-10.0),
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "error when sensor_id is empty",
|
|
given: Sensor{
|
|
SensorID: "",
|
|
SensorType: "temperature",
|
|
},
|
|
expecErr: true,
|
|
},
|
|
{
|
|
name: "error when sensor_type is empty",
|
|
given: Sensor{
|
|
SensorID: "temp-001",
|
|
SensorType: "",
|
|
},
|
|
expecErr: true,
|
|
},
|
|
{
|
|
name: "sensor type not in const",
|
|
given: Sensor{
|
|
SensorID: "temp-001",
|
|
SensorType: "unknown",
|
|
},
|
|
expecErr: true,
|
|
},
|
|
{
|
|
name: "default sampling_interval when nil",
|
|
given: Sensor{
|
|
SensorID: "temp-002",
|
|
SensorType: "humidity",
|
|
},
|
|
expected: Sensor{
|
|
SensorID: "temp-002",
|
|
SensorType: "humidity",
|
|
SamplingInterval: ptr(time.Second * 3600),
|
|
ThresholdAbove: ptr(100.0),
|
|
ThresholdBelow: ptr(0.0),
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "default threshold_above when nil",
|
|
given: Sensor{
|
|
SensorID: "temp-003",
|
|
SensorType: "pressure",
|
|
SamplingInterval: ptr(time.Minute * 5),
|
|
},
|
|
expected: Sensor{
|
|
SensorID: "temp-003",
|
|
SensorType: "pressure",
|
|
SamplingInterval: ptr(time.Minute * 5),
|
|
ThresholdAbove: ptr(100.0),
|
|
ThresholdBelow: ptr(0.0),
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "default threshold_below when nil",
|
|
given: Sensor{
|
|
SensorID: "temp-004",
|
|
SensorType: "light",
|
|
SamplingInterval: ptr(time.Second * 30),
|
|
ThresholdAbove: ptr(200.0),
|
|
},
|
|
expected: Sensor{
|
|
SensorID: "temp-004",
|
|
SensorType: "light",
|
|
SamplingInterval: ptr(time.Second * 30),
|
|
ThresholdAbove: ptr(200.0),
|
|
ThresholdBelow: ptr(0.0),
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "zero values are preserved",
|
|
given: Sensor{
|
|
SensorID: "temp-005",
|
|
SensorType: "temperature",
|
|
SamplingInterval: ptr(time.Second * 10),
|
|
ThresholdAbove: ptr(0.0),
|
|
ThresholdBelow: ptr(0.0),
|
|
},
|
|
expected: Sensor{
|
|
SensorID: "temp-005",
|
|
SensorType: "temperature",
|
|
SamplingInterval: ptr(time.Second * 10),
|
|
ThresholdAbove: ptr(0.0),
|
|
ThresholdBelow: ptr(0.0),
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "negative threshold_below is valid",
|
|
given: Sensor{
|
|
SensorID: "temp-006",
|
|
SensorType: "temperature",
|
|
SamplingInterval: ptr(time.Minute * 2),
|
|
ThresholdAbove: ptr(35.0),
|
|
ThresholdBelow: ptr(-20.5),
|
|
},
|
|
expected: Sensor{
|
|
SensorID: "temp-006",
|
|
SensorType: "temperature",
|
|
SamplingInterval: ptr(time.Minute * 2),
|
|
ThresholdAbove: ptr(35.0),
|
|
ThresholdBelow: ptr(-20.5),
|
|
},
|
|
expecErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.given.Validate()
|
|
|
|
if tt.expecErr && err == nil {
|
|
t.Errorf("expected error, got nil")
|
|
return
|
|
}
|
|
|
|
if !tt.expecErr && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
|
|
if tt.expecErr {
|
|
return
|
|
}
|
|
|
|
if tt.given.SensorID != tt.expected.SensorID {
|
|
t.Errorf("SensorID: expected %q, got %q", tt.expected.SensorID, tt.given.SensorID)
|
|
}
|
|
|
|
if tt.given.SensorType != tt.expected.SensorType {
|
|
t.Errorf("expected %q, got %q", tt.expected.SensorType, tt.given.SensorType)
|
|
}
|
|
|
|
if tt.given.SamplingInterval == nil || tt.expected.SamplingInterval == nil {
|
|
if tt.given.SamplingInterval != tt.expected.SamplingInterval {
|
|
t.Errorf("expected %v, got %v", tt.expected.SamplingInterval, tt.given.SamplingInterval)
|
|
}
|
|
} else if *tt.given.SamplingInterval != *tt.expected.SamplingInterval {
|
|
t.Errorf("expected %v, got %v", *tt.expected.SamplingInterval, *tt.given.SamplingInterval)
|
|
}
|
|
|
|
if tt.given.ThresholdAbove == nil || tt.expected.ThresholdAbove == nil {
|
|
if tt.given.ThresholdAbove != tt.expected.ThresholdAbove {
|
|
t.Errorf("expected %v, got %v", tt.expected.ThresholdAbove, tt.given.ThresholdAbove)
|
|
}
|
|
} else if *tt.given.ThresholdAbove != *tt.expected.ThresholdAbove {
|
|
t.Errorf("expected %v, got %v", *tt.expected.ThresholdAbove, *tt.given.ThresholdAbove)
|
|
}
|
|
|
|
if tt.given.ThresholdBelow == nil || tt.expected.ThresholdBelow == nil {
|
|
if tt.given.ThresholdBelow != tt.expected.ThresholdBelow {
|
|
t.Errorf("expected %v, got %v", tt.expected.ThresholdBelow, tt.given.ThresholdBelow)
|
|
}
|
|
} else if *tt.given.ThresholdBelow != *tt.expected.ThresholdBelow {
|
|
t.Errorf("expected %v, got %v", *tt.expected.ThresholdBelow, *tt.given.ThresholdBelow)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SensorData_IsOutOfRangeAbove(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
data SensorData
|
|
sensor Sensor
|
|
expected bool
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
name: "value above threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(150.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdAbove: ptr(100.0),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "value below threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(50.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdAbove: ptr(100.0),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "value equal to threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(100.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdAbove: ptr(100.0),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "negative value above negative threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(-5.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdAbove: ptr(-10.0),
|
|
},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.data.IsOutOfRangeAbove(tt.sensor)
|
|
|
|
if result != tt.expected {
|
|
t.Errorf("expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SensorData_IsOutOfRangeBelow(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
data SensorData
|
|
sensor Sensor
|
|
expected bool
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
name: "value below threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(5.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdBelow: ptr(10.0),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "value above threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(50.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdBelow: ptr(10.0),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "value equal to threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(10.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdBelow: ptr(10.0),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "negative value below threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(-15.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdBelow: ptr(-10.0),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "zero value below positive threshold",
|
|
data: SensorData{
|
|
SensorID: "temp-001",
|
|
Value: ptr(0.0),
|
|
Timestamp: ptr(time.Now()),
|
|
},
|
|
sensor: Sensor{
|
|
SensorID: "temp-001",
|
|
ThresholdBelow: ptr(5.0),
|
|
},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.data.IsOutOfRangeBelow(tt.sensor)
|
|
|
|
if result != tt.expected {
|
|
t.Errorf("expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SensorRequest_Validate(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
given SensorRequest
|
|
expecErr bool
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
name: "valid request with sensor_id",
|
|
given: SensorRequest{
|
|
SensorID: "temp-001",
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "error when sensor_id is empty",
|
|
given: SensorRequest{
|
|
SensorID: "",
|
|
},
|
|
expecErr: true,
|
|
},
|
|
{
|
|
name: "valid request with long sensor_id",
|
|
given: SensorRequest{
|
|
SensorID: "sensor-with-very-long-identifier-12345",
|
|
},
|
|
expecErr: false,
|
|
},
|
|
{
|
|
name: "valid request with special characters",
|
|
given: SensorRequest{
|
|
SensorID: "sensor-001_test",
|
|
},
|
|
expecErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.given.Validate()
|
|
|
|
if tt.expecErr && err == nil {
|
|
t.Errorf("expected error, got nil")
|
|
return
|
|
}
|
|
|
|
if !tt.expecErr && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SensorDataRequest_Validate(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
given SensorDataRequest
|
|
expecErr bool
|
|
checkFn func(t *testing.T, req SensorDataRequest)
|
|
}
|
|
|
|
now := time.Now()
|
|
weekAgo := now.AddDate(0, 0, -7)
|
|
validFrom := weekAgo.Format(time.RFC3339)
|
|
validTo := now.Format(time.RFC3339)
|
|
|
|
tests := []testCase{
|
|
{
|
|
name: "valid request with all fields",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: ptr(validFrom),
|
|
To: ptr(validTo),
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.From == nil || *req.From != validFrom {
|
|
t.Errorf("expected From to be %q, got %v", validFrom, req.From)
|
|
}
|
|
if req.To == nil || *req.To != validTo {
|
|
t.Errorf("expected To to be %q, got %v", validTo, req.To)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "error when sensor_id is empty",
|
|
given: SensorDataRequest{
|
|
SensorID: "",
|
|
From: ptr(validFrom),
|
|
To: ptr(validTo),
|
|
},
|
|
expecErr: true,
|
|
},
|
|
{
|
|
name: "default To when nil",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: ptr(validFrom),
|
|
To: nil,
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.To == nil {
|
|
t.Error("expected To to be set with default value")
|
|
return
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, *req.To)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format, got error: %v", err)
|
|
}
|
|
if time.Since(parsed) > time.Minute {
|
|
t.Error("expected To to be approximately now")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "default To when empty string",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: ptr(validFrom),
|
|
To: ptr(""),
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.To == nil {
|
|
t.Error("expected To to be set with default value")
|
|
return
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, *req.To)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format, got error: %v", err)
|
|
}
|
|
if time.Since(parsed) > time.Minute {
|
|
t.Error("expected To to be approximately now")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "default From when nil",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: nil,
|
|
To: ptr(validTo),
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.From == nil {
|
|
t.Error("expected From to be set with default value")
|
|
return
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, *req.From)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format, got error: %v", err)
|
|
}
|
|
expectedFrom := time.Now().AddDate(0, 0, -7)
|
|
diff := expectedFrom.Sub(parsed)
|
|
if diff > time.Hour || diff < -time.Hour {
|
|
t.Errorf("expected From to be approximately 7 days ago, got %v", parsed)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "default From when empty string",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: ptr(""),
|
|
To: ptr(validTo),
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.From == nil {
|
|
t.Error("expected From to be set with default value")
|
|
return
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, *req.From)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format, got error: %v", err)
|
|
}
|
|
expectedFrom := time.Now().AddDate(0, 0, -7)
|
|
diff := expectedFrom.Sub(parsed)
|
|
if diff > time.Hour || diff < -time.Hour {
|
|
t.Errorf("expected From to be approximately 7 days ago, got %v", parsed)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "invalid From format sets default",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: ptr("invalid-date"),
|
|
To: ptr(validTo),
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.From == nil {
|
|
t.Error("expected From to be set with default value")
|
|
return
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, *req.From)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format after correction, got error: %v", err)
|
|
}
|
|
expectedFrom := time.Now().AddDate(0, 0, -7)
|
|
diff := expectedFrom.Sub(parsed)
|
|
if diff > time.Hour || diff < -time.Hour {
|
|
t.Errorf("expected From to be approximately 7 days ago after correction, got %v", parsed)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "invalid To format sets default",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: ptr(validFrom),
|
|
To: ptr("not-a-date"),
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.To == nil {
|
|
t.Error("expected To to be set with default value")
|
|
return
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, *req.To)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format after correction, got error: %v", err)
|
|
}
|
|
if time.Since(parsed) > time.Minute {
|
|
t.Error("expected To to be approximately now after correction")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "all defaults when From and To are nil",
|
|
given: SensorDataRequest{
|
|
SensorID: "temp-001",
|
|
From: nil,
|
|
To: nil,
|
|
},
|
|
expecErr: false,
|
|
checkFn: func(t *testing.T, req SensorDataRequest) {
|
|
if req.From == nil || req.To == nil {
|
|
t.Error("expected both From and To to be set with defaults")
|
|
return
|
|
}
|
|
parsedFrom, err := time.Parse(time.RFC3339, *req.From)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format for From, got error: %v", err)
|
|
}
|
|
parsedTo, err := time.Parse(time.RFC3339, *req.To)
|
|
if err != nil {
|
|
t.Errorf("expected valid RFC3339 format for To, got error: %v", err)
|
|
}
|
|
expectedFrom := time.Now().AddDate(0, 0, -7)
|
|
diff := expectedFrom.Sub(parsedFrom)
|
|
if diff > time.Hour || diff < -time.Hour {
|
|
t.Errorf("expected From to be approximately 7 days ago, got %v", parsedFrom)
|
|
}
|
|
if time.Since(parsedTo) > time.Minute {
|
|
t.Error("expected To to be approximately now")
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.given.Validate()
|
|
|
|
if tt.expecErr && err == nil {
|
|
t.Errorf("expected error, got nil")
|
|
return
|
|
}
|
|
|
|
if !tt.expecErr && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
|
|
if tt.expecErr {
|
|
return
|
|
}
|
|
|
|
if tt.checkFn != nil {
|
|
tt.checkFn(t, tt.given)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func ptr[T any](v T) *T {
|
|
return &v
|
|
}
|