Compare commits

...

4 Commits

Author SHA1 Message Date
f5583b3cd5 add test for sensor data validate 2025-10-10 02:03:16 +02:00
d2f0e8ccf5 add tests for nil values 2025-10-10 02:00:48 +02:00
772ce9a7b7 add tdlr 2025-10-10 01:49:39 +02:00
57254fcbc9 add sensors to migration 2025-10-10 01:46:08 +02:00
4 changed files with 172 additions and 3 deletions

View File

@ -26,10 +26,14 @@ migrateup:
go run -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest -path app/database -database "postgresql://developer:secret@localhost:5432/$(DB_NAME)?sslmode=disable" -verbose up
.PHONY: mock
#Mock database
# Mock database
mock:
go run go.uber.org/mock/mockgen@latest -package mock -destination internal/domains/sensors/mock/querier.go $(MOD_NAME)/internal/domains/sensors Repository
.PHONY: test
# Run tests
tests:
go test ./...
.PHONY: run
# Start app in development environment

View File

@ -3,6 +3,11 @@
Lectura de datos de sensores en un dispositivo IoT. Prueba técnica para optar
por el puesto de programador Go.
## TL:DR
- make lazy-start
- abre terminal y escribe: `nats sub sensors.data.*`
## Requisitos previos
- Docker
@ -47,7 +52,8 @@ por el puesto de programador Go.
### Obtener valores de un sensor
- Campo obligatorio: `sensor_id`.
- Campos opcionales: `from` y `to` en formato RFC3339. Si no se especifican, se toman los últimos 7 días.
- Campos opcionales: `from` y `to` en formato RFC3339. Si no se especifican,
se toman los últimos 7 días.
`nats req sensors.values.get '{
"sensor_id": "sensor-001",

View File

@ -34,4 +34,13 @@ create table registry
timescaledb.hypertable,
timescaledb.partition_column = 'created_at',
timescaledb.segmentby = 'sensor_id'
);
);
insert into sensors (sensor_id, sensor_type, sampling_interval, threshold_above, threshold_below)
values
('temp-001', 'temperature', 10, 100, 0),
('hum-001', 'humidity', 15, 80, 20),
('co2-001', 'carbon_dioxide', 20, 1000, 400),
('pres-001', 'pressure', 30, 1050, 950),
('prox-001', 'proximity', 5, 200, 0),
('light-001', 'light', 10, 10000, 0);

View File

@ -195,6 +195,136 @@ func Test_SensorValidate(t *testing.T) {
}
}
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
@ -256,6 +386,16 @@ func Test_SensorData_IsOutOfRangeAbove(t *testing.T) {
},
expected: true,
},
{
name: "nil values",
data: SensorData{
SensorID: "temp-001",
},
sensor: Sensor{
SensorID: "temp-001",
},
expected: false,
},
}
for _, tt := range tests {
@ -343,6 +483,16 @@ func Test_SensorData_IsOutOfRangeBelow(t *testing.T) {
},
expected: true,
},
{
name: "nil values",
data: SensorData{
SensorID: "temp-001",
},
sensor: Sensor{
SensorID: "temp-001",
},
expected: false,
},
}
for _, tt := range tests {