nats-app/internal/domains/sensors/domain_test.go

803 lines
19 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_Validate(t *testing.T) {
type testCase struct {
name string
given SensorData
expected SensorData
expecErr bool
}
timestamp := time.Now()
tests := []testCase{
{
name: "success with all fields",
given: SensorData{
SensorID: "temp-001",
Value: ptr(25.5),
Timestamp: ptr(timestamp),
},
expected: SensorData{
SensorID: "temp-001",
Value: ptr(25.5),
Timestamp: ptr(timestamp),
},
expecErr: false,
},
{
name: "error when sensor_id is empty",
given: SensorData{
SensorID: "",
Value: ptr(25.5),
},
expecErr: true,
},
{
name: "error when value is nil",
given: SensorData{
SensorID: "temp-001",
Value: nil,
},
expecErr: true,
},
{
name: "default timestamp when nil",
given: SensorData{
SensorID: "temp-001",
Value: ptr(30.0),
Timestamp: nil,
},
expected: SensorData{
SensorID: "temp-001",
Value: ptr(30.0),
Timestamp: nil, // Will be set by Validate
},
expecErr: false,
},
{
name: "zero value is preserved",
given: SensorData{
SensorID: "temp-001",
Value: ptr(0.0),
Timestamp: ptr(timestamp),
},
expected: SensorData{
SensorID: "temp-001",
Value: ptr(0.0),
Timestamp: ptr(timestamp),
},
expecErr: false,
},
{
name: "negative value is valid",
given: SensorData{
SensorID: "temp-001",
Value: ptr(-15.5),
Timestamp: ptr(timestamp),
},
expected: SensorData{
SensorID: "temp-001",
Value: ptr(-15.5),
Timestamp: ptr(timestamp),
},
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.Value == nil || tt.expected.Value == nil {
if tt.given.Value != tt.expected.Value {
t.Errorf("Value: expected %v, got %v", tt.expected.Value, tt.given.Value)
}
} else if *tt.given.Value != *tt.expected.Value {
t.Errorf("Value: expected %v, got %v", *tt.expected.Value, *tt.given.Value)
}
if tt.expected.Timestamp == nil && tt.given.Timestamp != nil {
if time.Since(*tt.given.Timestamp) > time.Minute {
t.Errorf("Timestamp: expected default to be approximately now, got %v", *tt.given.Timestamp)
}
} else if tt.given.Timestamp == nil || tt.expected.Timestamp == nil {
if tt.given.Timestamp != tt.expected.Timestamp {
t.Errorf("Timestamp: expected %v, got %v", tt.expected.Timestamp, tt.given.Timestamp)
}
} else if !tt.given.Timestamp.Equal(*tt.expected.Timestamp) {
t.Errorf("Timestamp: expected %v, got %v", *tt.expected.Timestamp, *tt.given.Timestamp)
}
})
}
}
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,
},
{
name: "nil values",
data: SensorData{
SensorID: "temp-001",
},
sensor: Sensor{
SensorID: "temp-001",
},
expected: false,
},
}
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,
},
{
name: "nil values",
data: SensorData{
SensorID: "temp-001",
},
sensor: Sensor{
SensorID: "temp-001",
},
expected: false,
},
}
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
}