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)) } } }) } }