fix test error

This commit is contained in:
Pedro Pérez 2025-10-10 03:09:34 +02:00
parent 40ffee4d56
commit 7bc9f1c987
3 changed files with 445 additions and 8 deletions

View File

@ -33,7 +33,7 @@ mock:
.PHONY: test .PHONY: test
# Run tests # Run tests
tests: tests:
go test ./... go test ./... -cover
.PHONY: run .PHONY: run
# Start app in development environment # Start app in development environment

View File

@ -123,9 +123,9 @@ Por otro lado también hay un sistema de caché muy rudimentario, en memoria que
es un mapa de valores. es un mapa de valores.
Para el registro de valores y mantener ambos se ha usado el patrón decorador que Para el registro de valores y mantener ambos se ha usado el patrón decorador que
bajo un mismo _struct_ se incluye las dos implementaciones y se llama a ambas bajo un mismo _struct_ se incluye las dos implementaciones y registra cambios en
funciones. Desde la capa servicios sólo tiene que llamar al decorador sin saber ambas partes. Desde la capa servicios sólo tiene que llamar al decorador sin
los detalles de la implementación. saber los detalles de la implementación.
### Continuamos con los servicios ### Continuamos con los servicios
@ -151,6 +151,20 @@ documentación me quedé con los conceptos clave:
Esto es todo, entonces los controladores de la entidad _sensors_ están Esto es todo, entonces los controladores de la entidad _sensors_ están
constituidos por una serie de _endpoints_ haciendo las acciones que se solicita. constituidos por una serie de _endpoints_ haciendo las acciones que se solicita.
### El simulador
Basada en _gorutinas_ y canales, cuando se inicia el simulador, se crea un canal
para detener simuladores que están en ejecución para su actualización o
detención.
Cuando se registra un nuevo sensor, está la función SimulateSensor, que se
inicia como una _gorutina_ y usa el `SamplingInterval` para el canal `ticker`,
así llamar a `generateData` cada vez que toque.
Una vez que el dato está generado se hace una publicación al asunto _sensor.data_,
que al mismo tiempo, el _handler_ registerData lo captura al estar registrado
al mismo asunto _sensor.data_.
## Pruebas ## Pruebas
La realización de pruebas unitarias de lo que son los controladores de NATS me La realización de pruebas unitarias de lo que son los controladores de NATS me

View File

@ -1,6 +1,7 @@
package sensors package sensors
import ( import (
"errors"
"testing" "testing"
"time" "time"
@ -20,6 +21,7 @@ func Test_RegisterSensor(t *testing.T) {
given Sensor given Sensor
setupMock func(q *MockRepository, params Sensor) setupMock func(q *MockRepository, params Sensor)
expecErr bool expecErr bool
expectErr error
} }
tests := []testCase{ tests := []testCase{
@ -47,9 +49,25 @@ func Test_RegisterSensor(t *testing.T) {
ThresholdBelow: ptr(0.0), ThresholdBelow: ptr(0.0),
}, },
setupMock: func(q *MockRepository, params Sensor) { setupMock: func(q *MockRepository, params Sensor) {
q.EXPECT().CreateSensor(params).Return(ErrSensorAlreadyExists) q.EXPECT().CreateSensor(params).Return(errors.New("duplicate key value"))
}, },
expecErr: true, expecErr: true,
expectErr: ErrSensorAlreadyExists,
},
{
name: "error - some db error",
given: Sensor{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
},
setupMock: func(q *MockRepository, params Sensor) {
q.EXPECT().CreateSensor(params).Return(errors.New("some db error"))
},
expecErr: true,
expectErr: ErrRegisteringSensor,
}, },
} }
@ -71,8 +89,413 @@ func Test_RegisterSensor(t *testing.T) {
return return
} }
if tt.expecErr && err != ErrSensorAlreadyExists { if tt.expecErr && tt.expectErr != nil && err != tt.expectErr {
t.Errorf("expected ErrSensorAlreadyExists, got %v", err) t.Errorf("expected error %v, got %v", tt.expectErr, err)
}
})
}
}
func Test_RegisterSensorData(t *testing.T) {
type testCase struct {
name string
given SensorData
setupMock func(q *MockRepository, params SensorData)
expecErr bool
}
timestamp := time.Now()
value := 25.5
tests := []testCase{
{
name: "success - registers sensor data",
given: SensorData{
SensorID: "temp-001",
Value: &value,
Timestamp: &timestamp,
},
setupMock: func(q *MockRepository, params SensorData) {
q.EXPECT().CreateSensorData(params).Return(nil)
},
expecErr: false,
},
{
name: "error - database error",
given: SensorData{
SensorID: "temp-001",
Value: &value,
Timestamp: &timestamp,
},
setupMock: func(q *MockRepository, params SensorData) {
q.EXPECT().CreateSensorData(params).Return(errors.New("database error"))
},
expecErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, q := setup(t)
tt.setupMock(q, tt.given)
err := s.RegisterSensorData(tt.given)
if tt.expecErr && err == nil {
t.Error("expected error, got nil")
return
}
if !tt.expecErr && err != nil {
t.Errorf("expected no error, got %v", err)
}
})
}
}
func Test_UpdateSensor(t *testing.T) {
type testCase struct {
name string
given Sensor
setupMock func(q *MockRepository, params Sensor)
expecErr bool
expectErr error
}
tests := []testCase{
{
name: "success - updates sensor",
given: Sensor{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute * 2),
ThresholdAbove: ptr(120.0),
ThresholdBelow: ptr(10.0),
},
setupMock: func(q *MockRepository, params Sensor) {
q.EXPECT().UpdateSensor(params).Return(nil)
},
expecErr: false,
},
{
name: "error - sensor already exists (duplicate)",
given: Sensor{
SensorID: "temp-002",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
},
setupMock: func(q *MockRepository, params Sensor) {
q.EXPECT().UpdateSensor(params).Return(errors.New("duplicate key value"))
},
expecErr: true,
expectErr: ErrSensorAlreadyExists,
},
{
name: "error - general database error",
given: Sensor{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
},
setupMock: func(q *MockRepository, params Sensor) {
q.EXPECT().UpdateSensor(params).Return(errors.New("connection failed"))
},
expecErr: true,
expectErr: ErrUpdatingSensor,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, q := setup(t)
tt.setupMock(q, tt.given)
err := s.UpdateSensor(tt.given)
if tt.expecErr && err == nil {
t.Error("expected error, got nil")
return
}
if !tt.expecErr && err != nil {
t.Errorf("expected no error, got %v", err)
return
}
if tt.expecErr && tt.expectErr != nil && err != tt.expectErr {
t.Errorf("expected error %v, got %v", tt.expectErr, err)
}
})
}
}
func Test_GetSensor(t *testing.T) {
type testCase struct {
name string
given SensorRequest
setupMock func(q *MockRepository, sensorID string)
expected Sensor
expecErr bool
}
tests := []testCase{
{
name: "success - retrieves sensor",
given: SensorRequest{
SensorID: "temp-001",
},
setupMock: func(q *MockRepository, sensorID string) {
q.EXPECT().ReadSensor(sensorID).Return(Sensor{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
}, nil)
},
expected: Sensor{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
},
expecErr: false,
},
{
name: "error - sensor not found",
given: SensorRequest{
SensorID: "temp-999",
},
setupMock: func(q *MockRepository, sensorID string) {
q.EXPECT().ReadSensor(sensorID).Return(Sensor{}, ErrSensorNotFound)
},
expected: Sensor{},
expecErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, q := setup(t)
tt.setupMock(q, tt.given.SensorID)
result, err := s.GetSensor(tt.given)
if tt.expecErr && err == nil {
t.Error("expected error, got nil")
return
}
if !tt.expecErr && err != nil {
t.Errorf("expected no error, got %v", err)
return
}
if !tt.expecErr {
if result.SensorID != tt.expected.SensorID {
t.Errorf("expected sensor_id %q, got %q", tt.expected.SensorID, result.SensorID)
}
if result.SensorType != tt.expected.SensorType {
t.Errorf("expected sensor_type %q, got %q", tt.expected.SensorType, result.SensorType)
}
}
})
}
}
func Test_GetValues(t *testing.T) {
type testCase struct {
name string
given SensorDataRequest
setupMock func(q *MockRepository, sensorID string, from, to time.Time)
expected []SensorData
expecErr bool
}
now := time.Now()
weekAgo := now.AddDate(0, 0, -7)
fromStr := weekAgo.Format(time.RFC3339)
toStr := now.Format(time.RFC3339)
value1 := 25.5
value2 := 26.0
tests := []testCase{
{
name: "success - retrieves sensor data",
given: SensorDataRequest{
SensorID: "temp-001",
From: &fromStr,
To: &toStr,
},
setupMock: func(q *MockRepository, sensorID string, from, to time.Time) {
q.EXPECT().ReadSensorValues(sensorID, from, to).Return([]SensorData{
{
SensorID: "temp-001",
Value: &value1,
Timestamp: &weekAgo,
},
{
SensorID: "temp-001",
Value: &value2,
Timestamp: &now,
},
}, nil)
},
expected: []SensorData{
{
SensorID: "temp-001",
Value: &value1,
Timestamp: &weekAgo,
},
{
SensorID: "temp-001",
Value: &value2,
Timestamp: &now,
},
},
expecErr: false,
},
{
name: "error - database error",
given: SensorDataRequest{
SensorID: "temp-001",
From: &fromStr,
To: &toStr,
},
setupMock: func(q *MockRepository, sensorID string, from, to time.Time) {
q.EXPECT().ReadSensorValues(sensorID, from, to).Return([]SensorData{}, errors.New("database error"))
},
expected: []SensorData{},
expecErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, q := setup(t)
from, _ := time.Parse(time.RFC3339, *tt.given.From)
to, _ := time.Parse(time.RFC3339, *tt.given.To)
tt.setupMock(q, tt.given.SensorID, from, to)
result, err := s.GetValues(tt.given)
if tt.expecErr && err == nil {
t.Error("expected error, got nil")
return
}
if !tt.expecErr && err != nil {
t.Errorf("expected no error, got %v", err)
return
}
if !tt.expecErr {
if len(result) != len(tt.expected) {
t.Errorf("expected %d values, got %d", len(tt.expected), len(result))
}
}
})
}
}
func Test_ListSensors(t *testing.T) {
type testCase struct {
name string
setupMock func(q *MockRepository)
expected []Sensor
expecErr bool
}
tests := []testCase{
{
name: "success - retrieves all sensors",
setupMock: func(q *MockRepository) {
q.EXPECT().ReadAllSensors().Return([]Sensor{
{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
},
{
SensorID: "hum-001",
SensorType: Humidity,
SamplingInterval: ptr(time.Minute * 2),
ThresholdAbove: ptr(80.0),
ThresholdBelow: ptr(20.0),
},
}, nil)
},
expected: []Sensor{
{
SensorID: "temp-001",
SensorType: Temperature,
SamplingInterval: ptr(time.Minute),
ThresholdAbove: ptr(100.0),
ThresholdBelow: ptr(0.0),
},
{
SensorID: "hum-001",
SensorType: Humidity,
SamplingInterval: ptr(time.Minute * 2),
ThresholdAbove: ptr(80.0),
ThresholdBelow: ptr(20.0),
},
},
expecErr: false,
},
{
name: "success - empty list when no sensors",
setupMock: func(q *MockRepository) {
q.EXPECT().ReadAllSensors().Return([]Sensor{}, nil)
},
expected: []Sensor{},
expecErr: false,
},
{
name: "error - database error",
setupMock: func(q *MockRepository) {
q.EXPECT().ReadAllSensors().Return([]Sensor{}, errors.New("database error"))
},
expected: []Sensor{},
expecErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, q := setup(t)
tt.setupMock(q)
result, err := s.ListSensors()
if tt.expecErr && err == nil {
t.Error("expected error, got nil")
return
}
if !tt.expecErr && err != nil {
t.Errorf("expected no error, got %v", err)
return
}
if !tt.expecErr {
if len(result) != len(tt.expected) {
t.Errorf("expected %d sensors, got %d", len(tt.expected), len(result))
}
} }
}) })
} }