Compare commits
5 Commits
f5583b3cd5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e89d5a8f5 | |||
| 7bc9f1c987 | |||
| 40ffee4d56 | |||
| 8774b55d3d | |||
| 8e33f95cb4 |
@@ -28,12 +28,12 @@ migrateup:
|
|||||||
.PHONY: mock
|
.PHONY: mock
|
||||||
# Mock database
|
# Mock database
|
||||||
mock:
|
mock:
|
||||||
go run go.uber.org/mock/mockgen@latest -package mock -destination internal/domains/sensors/mock/querier.go $(MOD_NAME)/internal/domains/sensors Repository
|
go run go.uber.org/mock/mockgen@latest -package sensors -destination internal/domains/sensors/repository_mock.go $(MOD_NAME)/internal/domains/sensors Repository
|
||||||
|
|
||||||
.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
|
||||||
@@ -48,6 +48,7 @@ run-prod:
|
|||||||
.PHONY: lazy-start
|
.PHONY: lazy-start
|
||||||
lazy-start:
|
lazy-start:
|
||||||
# Install dependencies, tools, dockerize containers, run tests and run app.
|
# Install dependencies, tools, dockerize containers, run tests and run app.
|
||||||
|
go mod download
|
||||||
make dockerize-db
|
make dockerize-db
|
||||||
make dockerize-nats
|
make dockerize-nats
|
||||||
make run-prod
|
make run-prod
|
||||||
@@ -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,13 +151,31 @@ 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.
|
||||||
|
|
||||||
## LLMS
|
### El simulador
|
||||||
|
|
||||||
He usado Claude para la toma de decisiones y ayuda con el _boilerplate_, que no
|
Basada en _gorutinas_ y canales, cuando se inicia el simulador, se crea un canal
|
||||||
es poca cosa, además también se ha usado para la generación de las pruebas
|
para detener simuladores que están en ejecución para su actualización o
|
||||||
unitarias, además de resolución de algunos problemas complejos.
|
detención.
|
||||||
|
|
||||||
## Generadores de código
|
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
|
||||||
|
|
||||||
|
La realización de pruebas unitarias de lo que son los controladores de NATS me
|
||||||
|
han sido imposible hacerlas en condiciones, podría haber usado Claude pero es
|
||||||
|
que no daba pie con bola y no entendía nada, así que por la máxima transparencia
|
||||||
|
he optado por no incorporarlas.
|
||||||
|
|
||||||
|
Las pruebas más interesantes son las de reglas de negocio y validación, lo que
|
||||||
|
viene a ser los servicios y dominio.
|
||||||
|
|
||||||
|
## Generadores y otras librerías
|
||||||
|
|
||||||
Existen generadores de código para Golang, de hecho, se fomenta su desarrollo,
|
Existen generadores de código para Golang, de hecho, se fomenta su desarrollo,
|
||||||
hay un artículo interesante de Rob Pike [hablando sobre ello](https://go.dev/blog/generate).
|
hay un artículo interesante de Rob Pike [hablando sobre ello](https://go.dev/blog/generate).
|
||||||
@@ -182,4 +200,65 @@ específico (DSL).
|
|||||||
No se ha incorporado porque hay que instalar la herramienta que ejecutan las
|
No se ha incorporado porque hay que instalar la herramienta que ejecutan las
|
||||||
pruebas, y no quería correr el riesgo de que no funcionase en otro equipo o no
|
pruebas, y no quería correr el riesgo de que no funcionase en otro equipo o no
|
||||||
diesen los resultados esperados. Que se podría haber usado un contenedor Docker,
|
diesen los resultados esperados. Que se podría haber usado un contenedor Docker,
|
||||||
sí, pero la prueba no consiste en eso.
|
sí, pero la prueba no consiste en eso.
|
||||||
|
|
||||||
|
También se ha planteado incorporar la librería _testify_, descartado porque para
|
||||||
|
comprobar si existe el error y algunas comparaciones no era necesario meter una
|
||||||
|
dependencia más.
|
||||||
|
|
||||||
|
## Diagrama
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
El diagrama explica básicamente la estrucutra del proyecto en términos generales,
|
||||||
|
se demuestra que el dominio sensors solo se comunica al exterior mediante el
|
||||||
|
NATS y el _logger_.
|
||||||
|
|
||||||
|
Amarillo: exterior.
|
||||||
|
Morado: infraestructura.
|
||||||
|
Verde: dominio
|
||||||
|
Blanco: simulador, los _handlers_ está conectado sólo para llamar a la
|
||||||
|
gorutina, pero en la realidad debería ir independiente.
|
||||||
|
|
||||||
|
## Conclusión y cierre
|
||||||
|
|
||||||
|
Interesante reto donde realmente lo que más me ha costado son la realización
|
||||||
|
de pruebas unitarias, hay veces que me cuesta coger el concepto. Tengo la teoría
|
||||||
|
muy clara, pero a la hora de la verdad se me complica un poco las cosas. Además
|
||||||
|
que _mockear_ el sistema de mensajería debe tener su especial complejidad.
|
||||||
|
También ha sido algo complejo entender la concurrencia y los canales, no es algo
|
||||||
|
que haya trabajado en profundidad pero sí que se le ha puesto bastante empeño,
|
||||||
|
cariño y nivel de detalle.
|
||||||
|
|
||||||
|
He puesto mucho en valor la arquitectura limpia con un toque personal, no es DDD
|
||||||
|
puro ya que hay elementos que no deberían estar en dominio, pero en el proyecto
|
||||||
|
que estoy trabajando ahora mismo se está diseñando de la misma manera y está
|
||||||
|
funcionando muy bien.
|
||||||
|
|
||||||
|
También se ha evitado todo lo posible el uso de LLMs para la generación de
|
||||||
|
código, y su uso ha sido para la toma de decisiones arquitectónicas, discusión y
|
||||||
|
lectura rápida sobre los distintos funcionamientos de algunas librerías. En más
|
||||||
|
de una ocasión he cuestionado las respuestas que da, teniendo que verificar con
|
||||||
|
la documentación oficial. Pongo en valor mi capacidad para aprovechar la IA de
|
||||||
|
la mejor forma posible, verificando la información, además recalco que justo el
|
||||||
|
proyecto actual es una migración de un código en PHP completamente hecho con IA,
|
||||||
|
y se puede ver patrones y errores comunes que comete.
|
||||||
|
|
||||||
|
Soy consciente de que hay margen de mejora, por ejemplo con los tests o con la
|
||||||
|
documentación, se ha puesto especial esfuerzo y atención a que los nombres de
|
||||||
|
las funciones, variables, métodos, estructuras y paquetes sean lo más
|
||||||
|
autodescriptivos posibles. Se han puesto algunos comentarios. También hay
|
||||||
|
esfuerzo por permitir ejecutar el proyecto por primera vez con la mínima
|
||||||
|
intervención.
|
||||||
|
|
||||||
|
Un problema interesante que tuve que resolver, que como el sensor puede mandar
|
||||||
|
un valor ausente, el tipo `float64` al hacer el `unmarshal` se establece a 0.0,
|
||||||
|
con lo que se puede considerar válido, con lo cual su solución fue el uso de
|
||||||
|
puntero, si se descubre que es `nil` se considera no válido.
|
||||||
|
|
||||||
|
Digamos que este proyecto resuelve el problema que se propone, un sistema que
|
||||||
|
permite registrar y actualziar un sensor. Se puede ver su estado y los datos que
|
||||||
|
se recogen (simulados) se guardan en una base de datos.
|
||||||
|
|
||||||
|
Espero que el proyecto sea de vuestro agrado y podamos tener una siguiente
|
||||||
|
reunión.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -106,6 +106,8 @@ func (r *SensorDataRequest) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrRegisteringSensor = errors.New("error registering sensor")
|
||||||
|
ErrUpdatingSensor = errors.New("error updating sensor")
|
||||||
ErrInvalidSensorIdentifier = errors.New("sensor identifier is required")
|
ErrInvalidSensorIdentifier = errors.New("sensor identifier is required")
|
||||||
ErrInvalidSensorType = errors.New("sensor type is required")
|
ErrInvalidSensorType = errors.New("sensor type is required")
|
||||||
ErrSensorNotFound = errors.New("sensor not found")
|
ErrSensorNotFound = errors.New("sensor not found")
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"nats-app/internal/iot"
|
"nats-app/internal/iot"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
)
|
)
|
||||||
@@ -93,11 +92,6 @@ func (h *Handlers) SetupEndpoints() *Handlers {
|
|||||||
func (h *Handlers) register() {
|
func (h *Handlers) register() {
|
||||||
h.NATS.Subscribe(subjectSensorsRegister, func(msg *nats.Msg) {
|
h.NATS.Subscribe(subjectSensorsRegister, func(msg *nats.Msg) {
|
||||||
handleRequest(msg, func(req Sensor) (Sensor, error) {
|
handleRequest(msg, func(req Sensor) (Sensor, error) {
|
||||||
if err := req.Validate(); err != nil {
|
|
||||||
slog.Error("error validating sensor", "error", err)
|
|
||||||
return Sensor{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.service.RegisterSensor(req); err != nil {
|
if err := h.service.RegisterSensor(req); err != nil {
|
||||||
return Sensor{}, err
|
return Sensor{}, err
|
||||||
}
|
}
|
||||||
@@ -112,18 +106,7 @@ func (h *Handlers) register() {
|
|||||||
func (h *Handlers) registerData() {
|
func (h *Handlers) registerData() {
|
||||||
h.NATS.Subscribe(subjectSensorsData+"*", func(msg *nats.Msg) {
|
h.NATS.Subscribe(subjectSensorsData+"*", func(msg *nats.Msg) {
|
||||||
handlePublish(msg, func(data SensorData) error {
|
handlePublish(msg, func(data SensorData) error {
|
||||||
if err := data.Validate(); err != nil {
|
return h.service.RegisterSensorData(data)
|
||||||
slog.Error("error validating sensor data", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.service.RegisterSensorData(data); err != nil {
|
|
||||||
slog.Error("failed to save sensor data", "error", err, "sensor_id", data.SensorID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("sensor data saved", "sensor_id", data.SensorID, "value", data.Value)
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -131,12 +114,6 @@ func (h *Handlers) registerData() {
|
|||||||
func (h *Handlers) update() {
|
func (h *Handlers) update() {
|
||||||
h.NATS.Subscribe(subjectSensorsUpdate, func(msg *nats.Msg) {
|
h.NATS.Subscribe(subjectSensorsUpdate, func(msg *nats.Msg) {
|
||||||
handleRequest(msg, func(req Sensor) (Sensor, error) {
|
handleRequest(msg, func(req Sensor) (Sensor, error) {
|
||||||
slog.Debug("calling sensor.update", "payload", req)
|
|
||||||
|
|
||||||
if err := req.Validate(); err != nil {
|
|
||||||
return Sensor{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.service.UpdateSensor(req); err != nil {
|
if err := h.service.UpdateSensor(req); err != nil {
|
||||||
return Sensor{}, err
|
return Sensor{}, err
|
||||||
}
|
}
|
||||||
@@ -151,13 +128,7 @@ func (h *Handlers) update() {
|
|||||||
func (h *Handlers) get() {
|
func (h *Handlers) get() {
|
||||||
h.NATS.Subscribe(subjectSensorsGet, func(msg *nats.Msg) {
|
h.NATS.Subscribe(subjectSensorsGet, func(msg *nats.Msg) {
|
||||||
handleRequest(msg, func(req SensorRequest) (Sensor, error) {
|
handleRequest(msg, func(req SensorRequest) (Sensor, error) {
|
||||||
slog.Debug("calling sensor.get", "payload", req)
|
return h.service.GetSensor(req)
|
||||||
|
|
||||||
if err := req.Validate(); err != nil {
|
|
||||||
return Sensor{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.service.GetSensor(req.SensorID)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -165,21 +136,7 @@ func (h *Handlers) get() {
|
|||||||
func (h *Handlers) getValues() {
|
func (h *Handlers) getValues() {
|
||||||
h.NATS.Subscribe(subjectSensorsValuesGet, func(msg *nats.Msg) {
|
h.NATS.Subscribe(subjectSensorsValuesGet, func(msg *nats.Msg) {
|
||||||
handleRequest(msg, func(req SensorDataRequest) ([]SensorData, error) {
|
handleRequest(msg, func(req SensorDataRequest) ([]SensorData, error) {
|
||||||
if err := req.Validate(); err != nil {
|
return h.service.GetValues(req)
|
||||||
return []SensorData{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
from, err := time.Parse(time.RFC3339, *req.From)
|
|
||||||
if err != nil {
|
|
||||||
return []SensorData{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
to, err := time.Parse(time.RFC3339, *req.To)
|
|
||||||
if err != nil {
|
|
||||||
return []SensorData{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.service.GetValues(req.SensorID, from, to)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-12
@@ -3,14 +3,13 @@
|
|||||||
//
|
//
|
||||||
// Generated by this command:
|
// Generated by this command:
|
||||||
//
|
//
|
||||||
// mockgen -package mock -destination internal/domains/sensors/mock/querier.go nats-app/internal/domains/sensors Repository
|
// mockgen -package sensors -destination internal/domains/sensors/repository_mock.go nats-app/internal/domains/sensors Repository
|
||||||
//
|
//
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package sensors is a generated GoMock package.
|
||||||
package mock
|
package sensors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
sensors "nats-app/internal/domains/sensors"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateSensor mocks base method.
|
// CreateSensor mocks base method.
|
||||||
func (m *MockRepository) CreateSensor(s sensors.Sensor) error {
|
func (m *MockRepository) CreateSensor(s Sensor) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreateSensor", s)
|
ret := m.ctrl.Call(m, "CreateSensor", s)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -55,11 +54,25 @@ func (mr *MockRepositoryMockRecorder) CreateSensor(s any) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSensor", reflect.TypeOf((*MockRepository)(nil).CreateSensor), s)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSensor", reflect.TypeOf((*MockRepository)(nil).CreateSensor), s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSensorData mocks base method.
|
||||||
|
func (m *MockRepository) CreateSensorData(data SensorData) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateSensorData", data)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSensorData indicates an expected call of CreateSensorData.
|
||||||
|
func (mr *MockRepositoryMockRecorder) CreateSensorData(data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSensorData", reflect.TypeOf((*MockRepository)(nil).CreateSensorData), data)
|
||||||
|
}
|
||||||
|
|
||||||
// ReadAllSensors mocks base method.
|
// ReadAllSensors mocks base method.
|
||||||
func (m *MockRepository) ReadAllSensors() ([]sensors.Sensor, error) {
|
func (m *MockRepository) ReadAllSensors() ([]Sensor, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ReadAllSensors")
|
ret := m.ctrl.Call(m, "ReadAllSensors")
|
||||||
ret0, _ := ret[0].([]sensors.Sensor)
|
ret0, _ := ret[0].([]Sensor)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -71,10 +84,10 @@ func (mr *MockRepositoryMockRecorder) ReadAllSensors() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadSensor mocks base method.
|
// ReadSensor mocks base method.
|
||||||
func (m *MockRepository) ReadSensor(sensorID string) (sensors.Sensor, error) {
|
func (m *MockRepository) ReadSensor(sensorID string) (Sensor, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ReadSensor", sensorID)
|
ret := m.ctrl.Call(m, "ReadSensor", sensorID)
|
||||||
ret0, _ := ret[0].(sensors.Sensor)
|
ret0, _ := ret[0].(Sensor)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -86,10 +99,10 @@ func (mr *MockRepositoryMockRecorder) ReadSensor(sensorID any) *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadSensorValues mocks base method.
|
// ReadSensorValues mocks base method.
|
||||||
func (m *MockRepository) ReadSensorValues(sensorID string, from, to time.Time) ([]sensors.SensorData, error) {
|
func (m *MockRepository) ReadSensorValues(sensorID string, from, to time.Time) ([]SensorData, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ReadSensorValues", sensorID, from, to)
|
ret := m.ctrl.Call(m, "ReadSensorValues", sensorID, from, to)
|
||||||
ret0, _ := ret[0].([]sensors.SensorData)
|
ret0, _ := ret[0].([]SensorData)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -101,7 +114,7 @@ func (mr *MockRepositoryMockRecorder) ReadSensorValues(sensorID, from, to any) *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSensor mocks base method.
|
// UpdateSensor mocks base method.
|
||||||
func (m *MockRepository) UpdateSensor(s sensors.Sensor) error {
|
func (m *MockRepository) UpdateSensor(s Sensor) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateSensor", s)
|
ret := m.ctrl.Call(m, "UpdateSensor", s)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -2,6 +2,7 @@ package sensors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,20 +17,32 @@ func NewService(repo Repository) *Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) RegisterSensor(sensor Sensor) error {
|
func (s *Service) RegisterSensor(sensor Sensor) error {
|
||||||
|
if err := sensor.Validate(); err != nil {
|
||||||
|
slog.Error("error validating sensor", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := s.repo.CreateSensor(sensor)
|
err := s.repo.CreateSensor(sensor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error registering sensor", "error", err)
|
slog.Error("error registering sensor", "error", err)
|
||||||
return err
|
if strings.Contains(err.Error(), "duplicate key value") {
|
||||||
|
return ErrSensorAlreadyExists
|
||||||
|
}
|
||||||
|
return ErrRegisteringSensor
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) RegisterSensorData(data SensorData) error {
|
func (s *Service) RegisterSensorData(data SensorData) error {
|
||||||
|
if err := data.Validate(); err != nil {
|
||||||
|
slog.Error("error validating sensor data", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := s.repo.CreateSensorData(data)
|
err := s.repo.CreateSensorData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error registering sensor data")
|
slog.Error("error registering sensor data", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,15 +50,51 @@ func (s *Service) RegisterSensorData(data SensorData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateSensor(sensor Sensor) error {
|
func (s *Service) UpdateSensor(sensor Sensor) error {
|
||||||
return s.repo.UpdateSensor(sensor)
|
if err := sensor.Validate(); err != nil {
|
||||||
|
slog.Error("error validating sensor data", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.repo.UpdateSensor(sensor)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error updating sensor", "error", err)
|
||||||
|
if strings.Contains(err.Error(), "duplicate key value") {
|
||||||
|
return ErrSensorAlreadyExists
|
||||||
|
}
|
||||||
|
return ErrUpdatingSensor
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetSensor(sensorID string) (Sensor, error) {
|
func (s *Service) GetSensor(sensor SensorRequest) (Sensor, error) {
|
||||||
return s.repo.ReadSensor(sensorID)
|
if err := sensor.Validate(); err != nil {
|
||||||
|
slog.Error("error getting sensor", "error", err)
|
||||||
|
return Sensor{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.ReadSensor(sensor.SensorID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetValues(sensorID string, from, to time.Time) ([]SensorData, error) {
|
func (s *Service) GetValues(sensor SensorDataRequest) ([]SensorData, error) {
|
||||||
return s.repo.ReadSensorValues(sensorID, from, to)
|
if err := sensor.Validate(); err != nil {
|
||||||
|
slog.Error("error validating sensor data request", "error", err)
|
||||||
|
return []SensorData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
from, err := time.Parse(time.RFC3339, *sensor.From)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error parsing from date", "error", err)
|
||||||
|
return []SensorData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
to, err := time.Parse(time.RFC3339, *sensor.To)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error parsing to date", "error", err)
|
||||||
|
return []SensorData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.ReadSensorValues(sensor.SensorID, from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListSensors() ([]Sensor, error) {
|
func (s *Service) ListSensors() ([]Sensor, error) {
|
||||||
|
|||||||
@@ -0,0 +1,502 @@
|
|||||||
|
package sensors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*Service, *MockRepository) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
q := NewMockRepository(ctrl)
|
||||||
|
s := NewService(q)
|
||||||
|
return s, q
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_RegisterSensor(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
given Sensor
|
||||||
|
setupMock func(q *MockRepository, params Sensor)
|
||||||
|
expecErr bool
|
||||||
|
expectErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
name: "success - registers new sensor",
|
||||||
|
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(nil)
|
||||||
|
},
|
||||||
|
expecErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error - sensor already exists",
|
||||||
|
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("duplicate key value"))
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s, q := setup(t)
|
||||||
|
|
||||||
|
tt.setupMock(q, tt.given)
|
||||||
|
|
||||||
|
err := s.RegisterSensor(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_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: ×tamp,
|
||||||
|
},
|
||||||
|
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: ×tamp,
|
||||||
|
},
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,9 @@ func Start(nats *broker.NATS) *Simulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimulateSensor simula lo que es un sensor, se llama a ese método como una
|
||||||
|
// go-rutina separada. Hace uso del SamplingInterval como temporizador para
|
||||||
|
// el canal ticker.
|
||||||
func (s *Simulator) SimulateSensor(sensor Sensor) {
|
func (s *Simulator) SimulateSensor(sensor Sensor) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
stopChan := make(chan bool)
|
stopChan := make(chan bool)
|
||||||
@@ -60,6 +63,8 @@ func (s *Simulator) SimulateSensor(sensor Sensor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSensor para la gorutina que haya activa de dicho sensor, y comienza una
|
||||||
|
// nueva con el intervalo actualizado.
|
||||||
func (s *Simulator) UpdateSensor(sensor Sensor) {
|
func (s *Simulator) UpdateSensor(sensor Sensor) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
stopChan, exists := s.stopChannels[sensor.SensorID]
|
stopChan, exists := s.stopChannels[sensor.SensorID]
|
||||||
@@ -77,6 +82,7 @@ func (s *Simulator) UpdateSensor(sensor Sensor) {
|
|||||||
slog.Info("simulator updated for sensor", "sensor_id", sensor.SensorID, "new_interval", sensor.SamplingInterval)
|
slog.Info("simulator updated for sensor", "sensor_id", sensor.SensorID, "new_interval", sensor.SamplingInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateData genera datos aleatorios por cada tipo de sensor.
|
||||||
func (s *Simulator) generateData(sensor Sensor) SensorData {
|
func (s *Simulator) generateData(sensor Sensor) SensorData {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
data := SensorData{
|
data := SensorData{
|
||||||
|
|||||||
Reference in New Issue
Block a user