From 11fe3acd91792b8c8bfb1eeab745c0dbf5bcb89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sat, 18 Jan 2025 15:31:51 +0100 Subject: [PATCH 01/17] feat: set watering status if a tree is created --- internal/service/domain/tree/tree.go | 10 +++++-- internal/service/domain/tree/tree_test.go | 4 +++ internal/service/domain/tree/utils_test.go | 34 +++++++++++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/internal/service/domain/tree/tree.go b/internal/service/domain/tree/tree.go index d3516da6..91ed9a78 100644 --- a/internal/service/domain/tree/tree.go +++ b/internal/service/domain/tree/tree.go @@ -146,12 +146,17 @@ func (s *TreeService) Create(ctx context.Context, treeCreate *entities.TreeCreat } if treeCreate.SensorID != nil { - sensorID, err := s.sensorRepo.GetByID(ctx, *treeCreate.SensorID) + sensor, err := s.sensorRepo.GetByID(ctx, *treeCreate.SensorID) if err != nil { log.Debug("failed to fetch sensor by id specified in the tree create request", "sensor_id", treeCreate.SensorID) return nil, service.MapError(ctx, err, service.ErrorLogEntityNotFound) } - fn = append(fn, tree.WithSensor(sensorID)) + fn = append(fn, tree.WithSensor(sensor)) + + if sensor.LatestData != nil && sensor.LatestData.Data != nil && len(sensor.LatestData.Data.Watermarks) > 0 { + status := utils.CalculateWateringStatus(treeCreate.PlantingYear, sensor.LatestData.Data.Watermarks) + fn = append(fn, tree.WithWateringStatus(status)) + } } fn = append(fn, @@ -202,6 +207,7 @@ func (s *TreeService) Update(ctx context.Context, id int32, tu *entities.TreeUpd return nil, service.MapError(ctx, err, service.ErrorLogEntityNotFound) } + // TODO: Why is this still commented out? // Check if the tree is readonly (imported from csv) // if currentTree.Readonly { // return nil, handleError(fmt.Errorf("tree with ID %d is readonly and cannot be updated", id)) diff --git a/internal/service/domain/tree/tree_test.go b/internal/service/domain/tree/tree_test.go index f469ee0d..120bbec3 100644 --- a/internal/service/domain/tree/tree_test.go +++ b/internal/service/domain/tree/tree_test.go @@ -251,6 +251,7 @@ func TestTreeService_Create(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(expectedTree, nil) // when @@ -359,6 +360,7 @@ func TestTreeService_Create(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(nil, expectedError) // when @@ -509,6 +511,7 @@ func TestTreeService_Update(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(updatedTree, nil) // when @@ -655,6 +658,7 @@ func TestTreeService_Update(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(nil, expectedError) // when diff --git a/internal/service/domain/tree/utils_test.go b/internal/service/domain/tree/utils_test.go index 8b2cfa05..8555b9da 100644 --- a/internal/service/domain/tree/utils_test.go +++ b/internal/service/domain/tree/utils_test.go @@ -51,6 +51,7 @@ var ( Description: "A mature oak tree", PlantingYear: 2023, Readonly: true, + WateringStatus: entities.WateringStatusBad, }, { ID: 2, @@ -63,6 +64,7 @@ var ( Description: "A young pine tree", PlantingYear: 2023, Readonly: true, + WateringStatus: entities.WateringStatusUnknown, }, } @@ -74,7 +76,7 @@ var ( Status: entities.SensorStatusUnknown, Latitude: 54.82124518093376, Longitude: 9.485702120628517, - LatestData: &entities.SensorData{}, + LatestData: TestSensorDataBad, }, { ID: "sensor-2", @@ -115,4 +117,34 @@ var ( Longitude: testLongitude, Description: "Updated description", } + + TestSensorDataBad = &entities.SensorData{ + ID: 1, + SensorID: "sensor-1", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Data: &entities.MqttPayload{ + Device: "sensor-1", + Temperature: 2.0, + Humidity: 0.5, + Battery: 3.3, + Watermarks: []entities.Watermark{ + { + Resistance: 2000, + Centibar: 80, + Depth: 30, + }, + { + Resistance: 2200, + Centibar: 85, + Depth: 60, + }, + { + Resistance: 2500, + Centibar: 90, + Depth: 90, + }, + }, + }, + } ) From f528b7940f14c2acf6c1172575af599b3b2dafb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 14:39:24 +0100 Subject: [PATCH 02/17] refactor: move handle_new_sensor_data out of tree service --- .../domain/tree/handle_new_sensor_data.go | 37 ++++++++++ .../tree/handle_new_sensor_data_test.go | 71 +++++++++++++++++++ internal/service/domain/tree/tree.go | 26 ------- 3 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 internal/service/domain/tree/handle_new_sensor_data.go create mode 100644 internal/service/domain/tree/handle_new_sensor_data_test.go diff --git a/internal/service/domain/tree/handle_new_sensor_data.go b/internal/service/domain/tree/handle_new_sensor_data.go new file mode 100644 index 00000000..87173c9d --- /dev/null +++ b/internal/service/domain/tree/handle_new_sensor_data.go @@ -0,0 +1,37 @@ +package tree + +import ( + "context" + "log/slog" + + "github.com/green-ecolution/green-ecolution-backend/internal/entities" + "github.com/green-ecolution/green-ecolution-backend/internal/logger" + "github.com/green-ecolution/green-ecolution-backend/internal/service/domain/utils" + "github.com/green-ecolution/green-ecolution-backend/internal/storage/postgres/tree" +) + +func (s *TreeService) HandleNewSensorData(ctx context.Context, event *entities.EventNewSensorData) error { + log := logger.GetLogger(ctx) + log.Debug("handle event", "event", event.Type(), "service", "TreeService") + t, err := s.treeRepo.GetBySensorID(ctx, event.New.SensorID) + if err != nil { + log.Error("failed to get tree by sensor id", "sensor_id", event.New.SensorID, "err", err) + return nil + } + + status := utils.CalculateWateringStatus(ctx, t.PlantingYear, event.New.Data.Watermarks) + + if status == t.WateringStatus { + return nil + } + + newTree, err := s.treeRepo.Update(ctx, t.ID, tree.WithWateringStatus(status)) + if err != nil { + log.Error("failed to update tree with new watering status", "tree_id", t.ID, "watering_status", status, "err", err) + } + + slog.Info("updating tree watering status", "prev_status", t.WateringStatus, "new_status", status) + + s.publishUpdateTreeEvent(ctx, t, newTree) + return nil +} diff --git a/internal/service/domain/tree/handle_new_sensor_data_test.go b/internal/service/domain/tree/handle_new_sensor_data_test.go new file mode 100644 index 00000000..82a6dae2 --- /dev/null +++ b/internal/service/domain/tree/handle_new_sensor_data_test.go @@ -0,0 +1,71 @@ +package tree + +import ( + "context" + "testing" + "time" + + "github.com/green-ecolution/green-ecolution-backend/internal/entities" + storageMock "github.com/green-ecolution/green-ecolution-backend/internal/storage/_mock" + "github.com/green-ecolution/green-ecolution-backend/internal/worker" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + + +func TestTreeService_HandleNewSensorData(t *testing.T) { + t.Run("should update watering status on new sensor data event", func(t *testing.T) { + treeRepo := storageMock.NewMockTreeRepository(t) + sensorRepo := storageMock.NewMockSensorRepository(t) + imageRepo := storageMock.NewMockImageRepository(t) + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) + svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) + + _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go eventManager.Run(ctx) + + sensorDataEvent := entities.SensorData{ + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 30, Depth: 30}, + {Centibar: 40, Depth: 60}, + {Centibar: 50, Depth: 90}, + }, + }, + } + + treeNew := entities.Tree{ + ID: 1, + PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusGood, + } + + tree := entities.Tree{ + ID: 1, + PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusUnknown, + } + + event := entities.NewEventSensorData(&sensorDataEvent) + + treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) + treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(&treeNew, nil) + + err := svc.HandleNewSensorData(context.Background(), &event) + + assert.NoError(t, err) + select { + case receivedEvent := <-ch: + e, ok := receivedEvent.(entities.EventUpdateTree) + assert.True(t, ok) + assert.Equal(t, *e.Prev, tree) + assert.Equal(t, *e.New, treeNew) + case <-time.After(100 * time.Millisecond): + t.Fatal("event was not received") + } + }) +} diff --git a/internal/service/domain/tree/tree.go b/internal/service/domain/tree/tree.go index 91ed9a78..7355798a 100644 --- a/internal/service/domain/tree/tree.go +++ b/internal/service/domain/tree/tree.go @@ -42,32 +42,6 @@ func NewTreeService( } } -func (s *TreeService) HandleNewSensorData(ctx context.Context, event *entities.EventNewSensorData) error { - log := logger.GetLogger(ctx) - log.Debug("handle event", "event", event.Type(), "service", "TreeService") - t, err := s.treeRepo.GetBySensorID(ctx, event.New.SensorID) - if err != nil { - log.Error("failed to get tree by sensor id", "sensor_id", event.New.SensorID, "err", err) - return nil - } - - status := utils.CalculateWateringStatus(ctx, t.PlantingYear, event.New.Data.Watermarks) - - if status == t.WateringStatus { - return nil - } - - newTree, err := s.treeRepo.Update(ctx, t.ID, tree.WithWateringStatus(status)) - if err != nil { - log.Error("failed to update tree with new watering status", "tree_id", t.ID, "watering_status", status, "err", err) - } - - slog.Info("updating tree watering status", "prev_status", t.WateringStatus, "new_status", status) - - s.publishUpdateTreeEvent(ctx, t, newTree) - return nil -} - func (s *TreeService) GetAll(ctx context.Context) ([]*entities.Tree, error) { log := logger.GetLogger(ctx) trees, err := s.treeRepo.GetAll(ctx) From c3598c0fed91a7f6a5f103ab5f8015fb5b0ded72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 14:39:52 +0100 Subject: [PATCH 03/17] feat: add other test cases on new sensor data --- .../tree/handle_new_sensor_data_test.go | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/internal/service/domain/tree/handle_new_sensor_data_test.go b/internal/service/domain/tree/handle_new_sensor_data_test.go index 82a6dae2..72ef1977 100644 --- a/internal/service/domain/tree/handle_new_sensor_data_test.go +++ b/internal/service/domain/tree/handle_new_sensor_data_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/green-ecolution/green-ecolution-backend/internal/entities" + "github.com/green-ecolution/green-ecolution-backend/internal/storage" storageMock "github.com/green-ecolution/green-ecolution-backend/internal/storage/_mock" "github.com/green-ecolution/green-ecolution-backend/internal/worker" "github.com/stretchr/testify/assert" @@ -54,7 +55,7 @@ func TestTreeService_HandleNewSensorData(t *testing.T) { treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(&treeNew, nil) - + err := svc.HandleNewSensorData(context.Background(), &event) assert.NoError(t, err) @@ -68,4 +69,95 @@ func TestTreeService_HandleNewSensorData(t *testing.T) { t.Fatal("event was not received") } }) + + t.Run("should not update and not send event if the sensor has no linked tree", func(t *testing.T) { + treeRepo := storageMock.NewMockTreeRepository(t) + sensorRepo := storageMock.NewMockSensorRepository(t) + imageRepo := storageMock.NewMockImageRepository(t) + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) + svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) + + // event + _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go eventManager.Run(ctx) + + sensorDataEvent := entities.SensorData{ + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 61, Depth: 30}, + {Centibar: 24, Depth: 60}, + {Centibar: 24, Depth: 90}, + }, + }, + } + + event := entities.NewEventSensorData(&sensorDataEvent) + + treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(nil, storage.ErrTreeNotFound) + + // when + err := svc.HandleNewSensorData(context.Background(), &event) + + // then + assert.NoError(t, err) + select { + case <-ch: + t.Fatal("event was received. It should not have been sent") + case <-time.After(100 * time.Millisecond): + assert.True(t, true) + } + }) + + t.Run("should not update and not send event if tree could not be updated", func(t *testing.T) { + treeRepo := storageMock.NewMockTreeRepository(t) + sensorRepo := storageMock.NewMockSensorRepository(t) + imageRepo := storageMock.NewMockImageRepository(t) + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) + svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) + + // event + _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go eventManager.Run(ctx) + + sensorDataEvent := entities.SensorData{ + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 30, Depth: 30}, + {Centibar: 40, Depth: 60}, + {Centibar: 50, Depth: 90}, + }, + }, + } + + tree := entities.Tree{ + ID: 1, + PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusUnknown, + } + + event := entities.NewEventSensorData(&sensorDataEvent) + + treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) + treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(nil, storage.ErrTreeNotFound) + + // when + err := svc.HandleNewSensorData(context.Background(), &event) + + // then + assert.NoError(t, err) + select { + case <-ch: + t.Fatal("event was received. It should not have been sent") + case <-time.After(100 * time.Millisecond): + assert.True(t, true) + } + }) } From b001d0234dc7b66876b927a6874f168713bb12f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sat, 18 Jan 2025 16:07:40 +0100 Subject: [PATCH 04/17] feat: update watering status on tree update --- internal/service/domain/tree/import_tree_test.go | 1 + internal/service/domain/tree/tree.go | 9 ++++++++- internal/service/domain/tree/tree_test.go | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/service/domain/tree/import_tree_test.go b/internal/service/domain/tree/import_tree_test.go index dad8f29a..a04e887a 100644 --- a/internal/service/domain/tree/import_tree_test.go +++ b/internal/service/domain/tree/import_tree_test.go @@ -65,6 +65,7 @@ func TestTreeService_ImportTree(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(updatedTree, nil) // When diff --git a/internal/service/domain/tree/tree.go b/internal/service/domain/tree/tree.go index 7355798a..d0619093 100644 --- a/internal/service/domain/tree/tree.go +++ b/internal/service/domain/tree/tree.go @@ -208,8 +208,15 @@ func (s *TreeService) Update(ctx context.Context, id int32, tu *entities.TreeUpd return nil, service.MapError(ctx, fmt.Errorf("failed to find Sensor with ID %v: %w", *tu.SensorID, err), service.ErrorLogEntityNotFound) } fn = append(fn, tree.WithSensor(sensor)) + + if sensor.LatestData != nil && sensor.LatestData.Data != nil && len(sensor.LatestData.Data.Watermarks) > 0 { + status := utils.CalculateWateringStatus(tu.PlantingYear, sensor.LatestData.Data.Watermarks) + fn = append(fn, tree.WithWateringStatus(status)) + } } else { - fn = append(fn, tree.WithSensor(nil)) + fn = append(fn, + tree.WithSensor(nil), + tree.WithWateringStatus(entities.WateringStatusUnknown)) } fn = append(fn, tree.WithPlantingYear(tu.PlantingYear), diff --git a/internal/service/domain/tree/tree_test.go b/internal/service/domain/tree/tree_test.go index 120bbec3..e500f73d 100644 --- a/internal/service/domain/tree/tree_test.go +++ b/internal/service/domain/tree/tree_test.go @@ -512,6 +512,7 @@ func TestTreeService_Update(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(updatedTree, nil) // when @@ -659,6 +660,7 @@ func TestTreeService_Update(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(nil, expectedError) // when @@ -747,6 +749,7 @@ func TestTreeService_EventSystem(t *testing.T) { mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything).Return(&expectedTree, nil) svc := tree.NewTreeService(treeRepo, sensorRepo, imageRepo, treeClusterRepo, eventManager) From 5340a0e7cb19843152a0af549f5b84ca1785abf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sat, 18 Jan 2025 16:20:12 +0100 Subject: [PATCH 05/17] fix: set watering status to unknown if a sensor is unlinked from a tree --- internal/storage/postgres/queries/trees.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/storage/postgres/queries/trees.sql b/internal/storage/postgres/queries/trees.sql index b028a1cc..f6f7388d 100644 --- a/internal/storage/postgres/queries/trees.sql +++ b/internal/storage/postgres/queries/trees.sql @@ -82,7 +82,9 @@ DELETE FROM trees WHERE id = $1 RETURNING id; UPDATE trees SET tree_cluster_id = NULL WHERE tree_cluster_id = $1 RETURNING id; -- name: UnlinkSensorIDFromTrees :exec -UPDATE trees SET sensor_id = NULL WHERE sensor_id = $1; +UPDATE trees +SET sensor_id = NULL, watering_status = 'unknown' +WHERE sensor_id = $1; -- name: CalculateGroupedCentroids :one SELECT ST_AsText(ST_Centroid(ST_Collect(geometry)))::text AS centroid FROM trees WHERE id = ANY($1::int[]); From f7527eb5ef7f614ca9b30387c25d423cc8f0d2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 14:43:11 +0100 Subject: [PATCH 06/17] feat: update tree cluster watering status on tree update, create, delete --- .../tree/handle_new_sensor_data_test.go | 175 +++++++++--------- internal/service/domain/tree/tree.go | 4 +- internal/service/domain/tree/utils_test.go | 66 +++---- .../treecluster/handle_new_sensor_data.go | 136 ++++++++------ .../domain/treecluster/handle_tree_update.go | 17 +- 5 files changed, 215 insertions(+), 183 deletions(-) diff --git a/internal/service/domain/tree/handle_new_sensor_data_test.go b/internal/service/domain/tree/handle_new_sensor_data_test.go index 72ef1977..8a6ebb08 100644 --- a/internal/service/domain/tree/handle_new_sensor_data_test.go +++ b/internal/service/domain/tree/handle_new_sensor_data_test.go @@ -13,70 +13,69 @@ import ( "github.com/stretchr/testify/mock" ) - func TestTreeService_HandleNewSensorData(t *testing.T) { - t.Run("should update watering status on new sensor data event", func(t *testing.T) { - treeRepo := storageMock.NewMockTreeRepository(t) - sensorRepo := storageMock.NewMockSensorRepository(t) - imageRepo := storageMock.NewMockImageRepository(t) - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) - svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) - - _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go eventManager.Run(ctx) - - sensorDataEvent := entities.SensorData{ - SensorID: "sensor-1", - Data: &entities.MqttPayload{ - Watermarks: []entities.Watermark{ - {Centibar: 30, Depth: 30}, - {Centibar: 40, Depth: 60}, - {Centibar: 50, Depth: 90}, - }, - }, - } - - treeNew := entities.Tree{ - ID: 1, - PlantingYear: int32(time.Now().Year() - 2), - WateringStatus: entities.WateringStatusGood, - } - - tree := entities.Tree{ - ID: 1, - PlantingYear: int32(time.Now().Year() - 2), - WateringStatus: entities.WateringStatusUnknown, - } - - event := entities.NewEventSensorData(&sensorDataEvent) - - treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) - treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(&treeNew, nil) - - err := svc.HandleNewSensorData(context.Background(), &event) - - assert.NoError(t, err) - select { - case receivedEvent := <-ch: - e, ok := receivedEvent.(entities.EventUpdateTree) - assert.True(t, ok) - assert.Equal(t, *e.Prev, tree) - assert.Equal(t, *e.New, treeNew) - case <-time.After(100 * time.Millisecond): - t.Fatal("event was not received") - } - }) + t.Run("should update watering status on new sensor data event", func(t *testing.T) { + treeRepo := storageMock.NewMockTreeRepository(t) + sensorRepo := storageMock.NewMockSensorRepository(t) + imageRepo := storageMock.NewMockImageRepository(t) + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) + svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) + + _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go eventManager.Run(ctx) + + sensorDataEvent := entities.SensorData{ + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 30, Depth: 30}, + {Centibar: 40, Depth: 60}, + {Centibar: 50, Depth: 90}, + }, + }, + } + + treeNew := entities.Tree{ + ID: 1, + PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusGood, + } + + tree := entities.Tree{ + ID: 1, + PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusUnknown, + } + + event := entities.NewEventSensorData(&sensorDataEvent) + + treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) + treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(&treeNew, nil) + + err := svc.HandleNewSensorData(context.Background(), &event) + + assert.NoError(t, err) + select { + case receivedEvent := <-ch: + e, ok := receivedEvent.(entities.EventUpdateTree) + assert.True(t, ok) + assert.Equal(t, *e.Prev, tree) + assert.Equal(t, *e.New, treeNew) + case <-time.After(100 * time.Millisecond): + t.Fatal("event was not received") + } + }) t.Run("should not update and not send event if the sensor has no linked tree", func(t *testing.T) { - treeRepo := storageMock.NewMockTreeRepository(t) - sensorRepo := storageMock.NewMockSensorRepository(t) - imageRepo := storageMock.NewMockImageRepository(t) - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) - svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) + treeRepo := storageMock.NewMockTreeRepository(t) + sensorRepo := storageMock.NewMockSensorRepository(t) + imageRepo := storageMock.NewMockImageRepository(t) + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) + svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) @@ -113,12 +112,12 @@ func TestTreeService_HandleNewSensorData(t *testing.T) { }) t.Run("should not update and not send event if tree could not be updated", func(t *testing.T) { - treeRepo := storageMock.NewMockTreeRepository(t) - sensorRepo := storageMock.NewMockSensorRepository(t) - imageRepo := storageMock.NewMockImageRepository(t) - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) - svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) + treeRepo := storageMock.NewMockTreeRepository(t) + sensorRepo := storageMock.NewMockSensorRepository(t) + imageRepo := storageMock.NewMockImageRepository(t) + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) + svc := NewTreeService(treeRepo, sensorRepo, imageRepo, clusterRepo, eventManager) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) @@ -126,27 +125,27 @@ func TestTreeService_HandleNewSensorData(t *testing.T) { defer cancel() go eventManager.Run(ctx) - sensorDataEvent := entities.SensorData{ - SensorID: "sensor-1", - Data: &entities.MqttPayload{ - Watermarks: []entities.Watermark{ - {Centibar: 30, Depth: 30}, - {Centibar: 40, Depth: 60}, - {Centibar: 50, Depth: 90}, - }, - }, - } - - tree := entities.Tree{ - ID: 1, - PlantingYear: int32(time.Now().Year() - 2), - WateringStatus: entities.WateringStatusUnknown, - } - - event := entities.NewEventSensorData(&sensorDataEvent) - - treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) - treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(nil, storage.ErrTreeNotFound) + sensorDataEvent := entities.SensorData{ + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 30, Depth: 30}, + {Centibar: 40, Depth: 60}, + {Centibar: 50, Depth: 90}, + }, + }, + } + + tree := entities.Tree{ + ID: 1, + PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusUnknown, + } + + event := entities.NewEventSensorData(&sensorDataEvent) + + treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) + treeRepo.EXPECT().Update(mock.Anything, mock.Anything, mock.Anything).Return(nil, storage.ErrTreeNotFound) // when err := svc.HandleNewSensorData(context.Background(), &event) diff --git a/internal/service/domain/tree/tree.go b/internal/service/domain/tree/tree.go index d0619093..605337fc 100644 --- a/internal/service/domain/tree/tree.go +++ b/internal/service/domain/tree/tree.go @@ -214,8 +214,8 @@ func (s *TreeService) Update(ctx context.Context, id int32, tu *entities.TreeUpd fn = append(fn, tree.WithWateringStatus(status)) } } else { - fn = append(fn, - tree.WithSensor(nil), + fn = append(fn, + tree.WithSensor(nil), tree.WithWateringStatus(entities.WateringStatusUnknown)) } diff --git a/internal/service/domain/tree/utils_test.go b/internal/service/domain/tree/utils_test.go index 8555b9da..fb5693ff 100644 --- a/internal/service/domain/tree/utils_test.go +++ b/internal/service/domain/tree/utils_test.go @@ -41,29 +41,29 @@ var ( TestTreesList = []*entities.Tree{ { - ID: 1, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Species: "Oak", - Number: "T001", - Latitude: testLatitude, - Longitude: testLongitude, - Description: "A mature oak tree", - PlantingYear: 2023, - Readonly: true, + ID: 1, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Species: "Oak", + Number: "T001", + Latitude: testLatitude, + Longitude: testLongitude, + Description: "A mature oak tree", + PlantingYear: 2023, + Readonly: true, WateringStatus: entities.WateringStatusBad, }, { - ID: 2, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Species: "Pine", - Number: "T002", - Latitude: 9.446700, - Longitude: 54.801510, - Description: "A young pine tree", - PlantingYear: 2023, - Readonly: true, + ID: 2, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Species: "Pine", + Number: "T002", + Latitude: 9.446700, + Longitude: 54.801510, + Description: "A young pine tree", + PlantingYear: 2023, + Readonly: true, WateringStatus: entities.WateringStatusUnknown, }, } @@ -119,30 +119,30 @@ var ( } TestSensorDataBad = &entities.SensorData{ - ID: 1, - SensorID: "sensor-1", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + ID: 1, + SensorID: "sensor-1", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), Data: &entities.MqttPayload{ - Device: "sensor-1", + Device: "sensor-1", Temperature: 2.0, - Humidity: 0.5, - Battery: 3.3, + Humidity: 0.5, + Battery: 3.3, Watermarks: []entities.Watermark{ { Resistance: 2000, - Centibar: 80, - Depth: 30, + Centibar: 80, + Depth: 30, }, { Resistance: 2200, - Centibar: 85, - Depth: 60, + Centibar: 85, + Depth: 60, }, { Resistance: 2500, - Centibar: 90, - Depth: 90, + Centibar: 90, + Depth: 90, }, }, }, diff --git a/internal/service/domain/treecluster/handle_new_sensor_data.go b/internal/service/domain/treecluster/handle_new_sensor_data.go index da8426e8..4db8d3b3 100644 --- a/internal/service/domain/treecluster/handle_new_sensor_data.go +++ b/internal/service/domain/treecluster/handle_new_sensor_data.go @@ -31,78 +31,104 @@ func (s *TreeClusterService) HandleNewSensorData(ctx context.Context, event *ent return nil } - sensorData, err := s.treeClusterRepo.GetAllLatestSensorDataByClusterID(ctx, tree.TreeCluster.ID) + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree) if err != nil { - log.Error("failed to get latest sensor data", "cluster_id", tree.TreeCluster.ID, "error", err) return nil } - var wateringStatus entities.WateringStatus - if len(sensorData) == 0 { - // assertion - if there is no sensor data after receiving the event, the world is ending + if wateringStatus == tree.TreeCluster.WateringStatus { return nil - } else if len(sensorData) == 1 { - wateringStatus = tree.WateringStatus - } else { - sensorIDs := utils.Map(sensorData, func(data *entities.SensorData) string { - return data.SensorID - }) - - trees, err := s.treeRepo.GetBySensorIDs(ctx, sensorIDs...) - if err != nil { - log.Error("failed to get trees by sensor id", "sensor_ids", sensorIDs, "error", err) - return nil - } + } - slices.SortFunc(trees, func(a, b *entities.Tree) int { - return int(b.PlantingYear - a.PlantingYear) - }) + updateFn := func(tc *entities.TreeCluster) (bool, error) { + tc.WateringStatus = wateringStatus + return true, nil + } - youngestTree := trees[0] + if err := s.treeClusterRepo.Update(ctx, tree.TreeCluster.ID, updateFn); err == nil { + return s.publishUpdateEvent(ctx, tree.TreeCluster) + } - var w30CentibarAvg, w60CentibarAvg, w90CentibarAvg int - for _, data := range sensorData { - w30, w60, w90, err := svcUtils.CheckAndSortWatermarks(data.Data.Watermarks) - if err != nil { - log.Error("sensor data watermarks are malformed", "watermarks", data.Data.Watermarks) - return nil - } + return nil +} - w30CentibarAvg += w30.Centibar - w60CentibarAvg += w60.Centibar - w90CentibarAvg += w90.Centibar - } +func (s *TreeClusterService) getWateringStatusOfTreeCluster(ctx context.Context, tree *entities.Tree) (entities.WateringStatus, error) { + log := logger.GetLogger(ctx) + sensorData, err := s.treeClusterRepo.GetAllLatestSensorDataByClusterID(ctx, tree.TreeCluster.ID) + if err != nil { + log.Error("failed to get latest sensor data", "cluster_id", tree.TreeCluster.ID, "err", err) + return entities.WateringStatusUnknown, errors.New("failed to get latest sensor data") + } - watermarks := []entities.Watermark{ - { - Centibar: w30CentibarAvg / len(sensorData), - Depth: 30, - }, - { - Centibar: w60CentibarAvg / len(sensorData), - Depth: 60, - }, - { - Centibar: w90CentibarAvg / len(sensorData), - Depth: 90, - }, - } + // assertion - if there is no sensor data after receiving the event, the world is ending + if len(sensorData) == 0 { + log.Error("sensor data is empty") + return entities.WateringStatusUnknown, errors.New("sensor data is empty") + } - wateringStatus = svcUtils.CalculateWateringStatus(ctx, youngestTree.PlantingYear, watermarks) + if len(sensorData) == 1 { + return tree.WateringStatus, nil } - if wateringStatus == tree.TreeCluster.WateringStatus { - return nil + sensorIDs := utils.Map(sensorData, func(data *entities.SensorData) string { + return data.SensorID + }) + + youngestTree, err := s.getYoungestTree(ctx, sensorIDs) + if err != nil { + return entities.WateringStatusUnknown, errors.New("failed to get youngest tree") } - updateFn := func(tc *entities.TreeCluster) (bool, error) { - tc.WateringStatus = wateringStatus - return true, nil + watermarks, err := s.getWatermarkSensorData(ctx, sensorData) + if err != nil { + return entities.WateringStatusUnknown, errors.New("failed getting watermark sensor data") } - if err := s.treeClusterRepo.Update(ctx, tree.TreeCluster.ID, updateFn); err == nil { - return s.publishUpdateEvent(ctx, tree.TreeCluster) + return svcUtils.CalculateWateringStatus(ctx, youngestTree.PlantingYear, watermarks), nil +} + +func (s *TreeClusterService) getYoungestTree(ctx context.Context, sensorIDs []string) (*entities.Tree, error) { + log := logger.GetLogger(ctx) + trees, err := s.treeRepo.GetBySensorIDs(ctx, sensorIDs...) + if err != nil { + log.Error("failed to get trees by sensor id", "sensor_ids", sensorIDs, "err", err) + return nil, errors.New("failed to get trees by sensor id") } - return nil + slices.SortFunc(trees, func(a, b *entities.Tree) int { + return int(b.PlantingYear - a.PlantingYear) + }) + + return trees[0], nil +} + +func (s *TreeClusterService) getWatermarkSensorData(ctx context.Context, sensorData []*entities.SensorData) ([]entities.Watermark, error) { + log := logger.GetLogger(ctx) + var w30CentibarAvg, w60CentibarAvg, w90CentibarAvg int + for _, data := range sensorData { + w30, w60, w90, err := svcUtils.CheckAndSortWatermarks(data.Data.Watermarks) + if err != nil { + log.Error("sensor data watermarks are malformed", "watermarks", data.Data.Watermarks) + return nil, errors.New("sensor data watermarks are malformed") + } + + w30CentibarAvg += w30.Centibar + w60CentibarAvg += w60.Centibar + w90CentibarAvg += w90.Centibar + } + + return []entities.Watermark{ + { + Centibar: w30CentibarAvg / len(sensorData), + Depth: 30, + }, + { + Centibar: w60CentibarAvg / len(sensorData), + Depth: 60, + }, + { + Centibar: w90CentibarAvg / len(sensorData), + Depth: 90, + }, + }, nil } diff --git a/internal/service/domain/treecluster/handle_tree_update.go b/internal/service/domain/treecluster/handle_tree_update.go index 68d9beec..4494464e 100644 --- a/internal/service/domain/treecluster/handle_tree_update.go +++ b/internal/service/domain/treecluster/handle_tree_update.go @@ -15,7 +15,7 @@ func (s *TreeClusterService) HandleCreateTree(ctx context.Context, event *entiti return nil } - return s.handleTreeClusterUpdate(ctx, event.New.TreeCluster) + return s.handleTreeClusterUpdate(ctx, event.New.TreeCluster, event.New) } func (s *TreeClusterService) HandleDeleteTree(ctx context.Context, event *entities.EventDeleteTree) error { @@ -26,7 +26,7 @@ func (s *TreeClusterService) HandleDeleteTree(ctx context.Context, event *entiti return nil } - return s.handleTreeClusterUpdate(ctx, event.Prev.TreeCluster) + return s.handleTreeClusterUpdate(ctx, event.Prev.TreeCluster, event.Prev) } func (s *TreeClusterService) HandleUpdateTree(ctx context.Context, event *entities.EventUpdateTree) error { @@ -41,12 +41,12 @@ func (s *TreeClusterService) HandleUpdateTree(ctx context.Context, event *entiti return nil } - if err := s.handleTreeClusterUpdate(ctx, event.Prev.TreeCluster); err != nil { + if err := s.handleTreeClusterUpdate(ctx, event.Prev.TreeCluster, event.New); err != nil { return err } if event.Prev.TreeCluster != nil && event.New.TreeCluster != nil && event.Prev.TreeCluster.ID != event.New.TreeCluster.ID { - if err := s.handleTreeClusterUpdate(ctx, event.New.TreeCluster); err != nil { + if err := s.handleTreeClusterUpdate(ctx, event.New.TreeCluster, event.New); err != nil { return err } } @@ -60,12 +60,17 @@ func (s *TreeClusterService) isNoUpdateNeeded(event *entities.EventUpdateTree) b return treePosSame && tcSame } -func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *entities.TreeCluster) error { +func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *entities.TreeCluster, tree *entities.Tree) error { log := logger.GetLogger(ctx) if tc == nil { return nil } + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree) + if err != nil { + return nil + } + updateFn := func(tc *entities.TreeCluster) (bool, error) { lat, long, region, err := s.getUpdatedLatLong(ctx, tc) if err != nil { @@ -75,6 +80,7 @@ func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *en tc.Latitude = lat tc.Longitude = long tc.Region = region + tc.WateringStatus = wateringStatus return true, nil } @@ -82,5 +88,6 @@ func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *en log.Info("successfully updated new tree cluster position", "cluster_id", tc.ID) return s.publishUpdateEvent(ctx, tc) } + return nil } From 8ec55ad0c3c1177a931069b84dbc8532c4946ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 19 Jan 2025 10:16:34 +0100 Subject: [PATCH 07/17] feat: update watering status on crud operations on a tree --- internal/service/domain/treecluster/handle_new_sensor_data.go | 4 ---- internal/service/domain/treecluster/handle_tree_update.go | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/service/domain/treecluster/handle_new_sensor_data.go b/internal/service/domain/treecluster/handle_new_sensor_data.go index 4db8d3b3..9f04e555 100644 --- a/internal/service/domain/treecluster/handle_new_sensor_data.go +++ b/internal/service/domain/treecluster/handle_new_sensor_data.go @@ -66,10 +66,6 @@ func (s *TreeClusterService) getWateringStatusOfTreeCluster(ctx context.Context, return entities.WateringStatusUnknown, errors.New("sensor data is empty") } - if len(sensorData) == 1 { - return tree.WateringStatus, nil - } - sensorIDs := utils.Map(sensorData, func(data *entities.SensorData) string { return data.SensorID }) diff --git a/internal/service/domain/treecluster/handle_tree_update.go b/internal/service/domain/treecluster/handle_tree_update.go index 4494464e..4ef9a69c 100644 --- a/internal/service/domain/treecluster/handle_tree_update.go +++ b/internal/service/domain/treecluster/handle_tree_update.go @@ -57,7 +57,8 @@ func (s *TreeClusterService) HandleUpdateTree(ctx context.Context, event *entiti func (s *TreeClusterService) isNoUpdateNeeded(event *entities.EventUpdateTree) bool { treePosSame := event.Prev.Latitude == event.New.Latitude && event.Prev.Longitude == event.New.Longitude tcSame := event.Prev.TreeCluster != nil && event.New.TreeCluster != nil && event.Prev.TreeCluster.ID == event.New.TreeCluster.ID - return treePosSame && tcSame + sensorSame := event.Prev.Sensor == event.New.Sensor + return treePosSame && tcSame && sensorSame } func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *entities.TreeCluster, tree *entities.Tree) error { From 8753ebd365f05c32effb1b677039fa5e31f2eab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 19 Jan 2025 11:01:07 +0100 Subject: [PATCH 08/17] fix: update tests to represent new watering status update on handle tree update --- .../handle_new_sensor_data_test.go | 29 ++++- .../domain/treecluster/handle_tree_update.go | 2 +- .../treecluster/handle_tree_update_test.go | 107 +++++++++++++++++- 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/internal/service/domain/treecluster/handle_new_sensor_data_test.go b/internal/service/domain/treecluster/handle_new_sensor_data_test.go index 316b631c..0f3f5fde 100644 --- a/internal/service/domain/treecluster/handle_new_sensor_data_test.go +++ b/internal/service/domain/treecluster/handle_new_sensor_data_test.go @@ -162,21 +162,31 @@ func TestTreeClusterService_HandleNewSensorData(t *testing.T) { } tree := entities.Tree{ - ID: 1, + ID: 1, + TreeCluster: tc, + PlantingYear: int32(time.Now().Year() - 2), + } + + treeWithSensorID1 := entities.Tree{ + ID: 2, TreeCluster: tc, - WateringStatus: entities.WateringStatusModerate, - PlantingYear: int32(time.Now().Year() - 2), + WateringStatus: entities.WateringStatusBad, + Sensor: &entities.Sensor{ + ID: "sensor-1", + }, + PlantingYear: int32(time.Now().Year() - 1), } event := entities.NewEventSensorData(&sensorDataEvent) treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return([]*entities.SensorData{&sensorDataEvent}, nil) + treeRepo.EXPECT().GetBySensorIDs(mock.Anything, "sensor-1").Return([]*entities.Tree{&treeWithSensorID1}, nil) clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).RunAndReturn(func(ctx context.Context, i int32, f func(*entities.TreeCluster) (bool, error)) error { cluster := entities.TreeCluster{} _, err := f(&cluster) assert.NoError(t, err) - assert.Equal(t, entities.WateringStatusModerate, cluster.WateringStatus) + assert.Equal(t, entities.WateringStatusBad, cluster.WateringStatus) return nil }) clusterRepo.EXPECT().GetByID(mock.Anything, int32(1)).Return(tcNew, nil) @@ -233,10 +243,21 @@ func TestTreeClusterService_HandleNewSensorData(t *testing.T) { PlantingYear: int32(time.Now().Year() - 2), } + treeWithSensorID1 := entities.Tree{ + ID: 2, + TreeCluster: tc, + WateringStatus: entities.WateringStatusBad, + Sensor: &entities.Sensor{ + ID: "sensor-1", + }, + PlantingYear: int32(time.Now().Year() - 1), + } + event := entities.NewEventSensorData(&sensorDataEvent) treeRepo.EXPECT().GetBySensorID(mock.Anything, "sensor-1").Return(&tree, nil) clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return([]*entities.SensorData{&sensorDataEvent}, nil) + treeRepo.EXPECT().GetBySensorIDs(mock.Anything, "sensor-1").Return([]*entities.Tree{&treeWithSensorID1}, nil) // when err := svc.HandleNewSensorData(context.Background(), &event) diff --git a/internal/service/domain/treecluster/handle_tree_update.go b/internal/service/domain/treecluster/handle_tree_update.go index 4ef9a69c..128bd089 100644 --- a/internal/service/domain/treecluster/handle_tree_update.go +++ b/internal/service/domain/treecluster/handle_tree_update.go @@ -69,7 +69,7 @@ func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *en wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree) if err != nil { - return nil + slog.Error("could not update watering status", "error", err) } updateFn := func(tc *entities.TreeCluster) (bool, error) { diff --git a/internal/service/domain/treecluster/handle_tree_update_test.go b/internal/service/domain/treecluster/handle_tree_update_test.go index 6ce2622f..f0d3c558 100644 --- a/internal/service/domain/treecluster/handle_tree_update_test.go +++ b/internal/service/domain/treecluster/handle_tree_update_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/green-ecolution/green-ecolution-backend/internal/entities" + "github.com/green-ecolution/green-ecolution-backend/internal/storage" storageMock "github.com/green-ecolution/green-ecolution-backend/internal/storage/_mock" "github.com/green-ecolution/green-ecolution-backend/internal/utils" "github.com/green-ecolution/green-ecolution-backend/internal/worker" @@ -64,7 +65,110 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { event := entities.NewEventUpdateTree(&prevTree, &updatedTree) - clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).Return(nil) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return(nil, storage.ErrSensorNotFound) + clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).RunAndReturn(func(ctx context.Context, i int32, f func(*entities.TreeCluster) (bool, error)) error { + cluster := entities.TreeCluster{} + _, err := f(&cluster) + assert.NoError(t, err) + // Watering status should be unknown due to no sensor data + assert.Equal(t, entities.WateringStatusUnknown, cluster.WateringStatus) + return nil + }) + clusterRepo.EXPECT().GetByID(mock.Anything, int32(1)).Return(&updatedTc, nil) + + // when + err := svc.HandleUpdateTree(context.Background(), &event) + + // then + assert.NoError(t, err) + select { + case recievedEvent, ok := <-ch: + assert.True(t, ok) + e := recievedEvent.(entities.EventUpdateTreeCluster) + assert.Equal(t, e.Prev, &prevTc) + assert.Equal(t, e.New, &updatedTc) + case <-time.After(1 * time.Second): + t.Fatal("event was not received") + } + }) + + t.Run("should update tree cluster watering status and send treecluster update event", func(t *testing.T) { + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + treeRepo := storageMock.NewMockTreeRepository(t) + regionRepo := storageMock.NewMockRegionRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) + svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + + // event + _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTreeCluster) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go eventManager.Run(ctx) + + prevTc := entities.TreeCluster{ + ID: 1, + Region: &entities.Region{ + ID: 1, + Name: "Sandberg", + }, + Latitude: utils.P(54.776366336440255), + Longitude: utils.P(9.451084144617182), + } + prevTree := entities.Tree{ + ID: 1, + TreeCluster: &prevTc, + Number: "T001", + Latitude: 54.776366336440255, + Longitude: 9.451084144617182, + PlantingYear: int32(time.Now().Year() - 2), + } + + updatedTree := entities.Tree{ + ID: 1, + TreeCluster: &prevTc, + Number: "T001", + Latitude: 54.811733806341856, + Longitude: 9.482958846410169, + PlantingYear: int32(time.Now().Year() - 2), + Sensor: &entities.Sensor{ + ID: "sensor-1", + }, + } + + updatedTc := entities.TreeCluster{ + ID: 1, + Region: &entities.Region{ + ID: 2, + Name: "Mürwik", + }, + Latitude: utils.P(54.811733806341856), + Longitude: utils.P(9.482958846410169), + } + + allLatestSensorData := []*entities.SensorData{ + { + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 61, Depth: 30}, + {Centibar: 24, Depth: 60}, + {Centibar: 23, Depth: 90}, + }, + }, + }, + } + + event := entities.NewEventUpdateTree(&prevTree, &updatedTree) + + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return(allLatestSensorData, nil) + treeRepo.EXPECT().GetBySensorIDs(mock.Anything, "sensor-1").Return([]*entities.Tree{&updatedTree}, nil) + clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).RunAndReturn(func(ctx context.Context, i int32, f func(*entities.TreeCluster) (bool, error)) error { + cluster := entities.TreeCluster{} + _, err := f(&cluster) + assert.NoError(t, err) + assert.Equal(t, entities.WateringStatusGood, cluster.WateringStatus) + return nil + }) clusterRepo.EXPECT().GetByID(mock.Anything, int32(1)).Return(&updatedTc, nil) // when @@ -237,6 +341,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { event := entities.NewEventUpdateTree(&prevTree, &updatedTree) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(2)).Return(nil, storage.ErrSensorNotFound) clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).Return(nil) clusterRepo.EXPECT().Update(mock.Anything, int32(2), mock.Anything).Return(nil) clusterRepo.EXPECT().GetByID(mock.Anything, int32(1)).Return(&prevTc, nil) From 43b9b0b5a0ccc1ef84f3df5d62bd2b1de15c4d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 19 Jan 2025 11:53:28 +0100 Subject: [PATCH 09/17] chore: simplify handle_tree_update_test --- .../treecluster/handle_tree_update_test.go | 257 ++++++------------ 1 file changed, 76 insertions(+), 181 deletions(-) diff --git a/internal/service/domain/treecluster/handle_tree_update_test.go b/internal/service/domain/treecluster/handle_tree_update_test.go index f0d3c558..04338e15 100644 --- a/internal/service/domain/treecluster/handle_tree_update_test.go +++ b/internal/service/domain/treecluster/handle_tree_update_test.go @@ -15,7 +15,7 @@ import ( ) func TestTreeClusterService_HandleUpdateTree(t *testing.T) { - t.Run("should update tree cluster lat long and region and send treecluster update event", func(t *testing.T) { + t.Run("should update tree cluster lat, long, region, watering status and send treecluster update event", func(t *testing.T) { clusterRepo := storageMock.NewMockTreeClusterRepository(t) treeRepo := storageMock.NewMockTreeRepository(t) regionRepo := storageMock.NewMockRegionRepository(t) @@ -28,50 +28,15 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { defer cancel() go eventManager.Run(ctx) - prevTc := entities.TreeCluster{ - ID: 1, - Region: &entities.Region{ - ID: 1, - Name: "Sandberg", - }, - Latitude: utils.P(54.776366336440255), - Longitude: utils.P(9.451084144617182), - } - prevTree := entities.Tree{ - ID: 1, - TreeCluster: &prevTc, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } - - updatedTree := entities.Tree{ - ID: 1, - TreeCluster: &prevTc, - Number: "T001", - Latitude: 54.811733806341856, - Longitude: 9.482958846410169, - } - - updatedTc := entities.TreeCluster{ - ID: 1, - Region: &entities.Region{ - ID: 2, - Name: "Mürwik", - }, - Latitude: utils.P(54.811733806341856), - Longitude: utils.P(9.482958846410169), - } - event := entities.NewEventUpdateTree(&prevTree, &updatedTree) - clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return(nil, storage.ErrSensorNotFound) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return(allLatestSensorData, nil) + treeRepo.EXPECT().GetBySensorIDs(mock.Anything, "sensor-1").Return([]*entities.Tree{&updatedTree}, nil) clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).RunAndReturn(func(ctx context.Context, i int32, f func(*entities.TreeCluster) (bool, error)) error { cluster := entities.TreeCluster{} _, err := f(&cluster) assert.NoError(t, err) - // Watering status should be unknown due to no sensor data - assert.Equal(t, entities.WateringStatusUnknown, cluster.WateringStatus) + assert.Equal(t, entities.WateringStatusGood, cluster.WateringStatus) return nil }) clusterRepo.EXPECT().GetByID(mock.Anything, int32(1)).Return(&updatedTc, nil) @@ -92,7 +57,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { } }) - t.Run("should update tree cluster watering status and send treecluster update event", func(t *testing.T) { + t.Run("should update tree cluster watering status to unkown and send treecluster update event", func(t *testing.T) { clusterRepo := storageMock.NewMockTreeClusterRepository(t) treeRepo := storageMock.NewMockTreeRepository(t) regionRepo := storageMock.NewMockRegionRepository(t) @@ -105,68 +70,14 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { defer cancel() go eventManager.Run(ctx) - prevTc := entities.TreeCluster{ - ID: 1, - Region: &entities.Region{ - ID: 1, - Name: "Sandberg", - }, - Latitude: utils.P(54.776366336440255), - Longitude: utils.P(9.451084144617182), - } - prevTree := entities.Tree{ - ID: 1, - TreeCluster: &prevTc, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - PlantingYear: int32(time.Now().Year() - 2), - } - - updatedTree := entities.Tree{ - ID: 1, - TreeCluster: &prevTc, - Number: "T001", - Latitude: 54.811733806341856, - Longitude: 9.482958846410169, - PlantingYear: int32(time.Now().Year() - 2), - Sensor: &entities.Sensor{ - ID: "sensor-1", - }, - } - - updatedTc := entities.TreeCluster{ - ID: 1, - Region: &entities.Region{ - ID: 2, - Name: "Mürwik", - }, - Latitude: utils.P(54.811733806341856), - Longitude: utils.P(9.482958846410169), - } - - allLatestSensorData := []*entities.SensorData{ - { - SensorID: "sensor-1", - Data: &entities.MqttPayload{ - Watermarks: []entities.Watermark{ - {Centibar: 61, Depth: 30}, - {Centibar: 24, Depth: 60}, - {Centibar: 23, Depth: 90}, - }, - }, - }, - } - event := entities.NewEventUpdateTree(&prevTree, &updatedTree) - clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return(allLatestSensorData, nil) - treeRepo.EXPECT().GetBySensorIDs(mock.Anything, "sensor-1").Return([]*entities.Tree{&updatedTree}, nil) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID(mock.Anything, int32(1)).Return(nil, storage.ErrSensorNotFound) clusterRepo.EXPECT().Update(mock.Anything, int32(1), mock.Anything).RunAndReturn(func(ctx context.Context, i int32, f func(*entities.TreeCluster) (bool, error)) error { cluster := entities.TreeCluster{} _, err := f(&cluster) assert.NoError(t, err) - assert.Equal(t, entities.WateringStatusGood, cluster.WateringStatus) + assert.Equal(t, entities.WateringStatusUnknown, cluster.WateringStatus) return nil }) clusterRepo.EXPECT().GetByID(mock.Anything, int32(1)).Return(&updatedTc, nil) @@ -200,23 +111,13 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { defer cancel() go eventManager.Run(ctx) - prevTree := entities.Tree{ - ID: 1, - TreeCluster: nil, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } + prevWithoutCluster := prevTree + prevWithoutCluster.TreeCluster = nil - updatedTree := entities.Tree{ - ID: 1, - TreeCluster: nil, - Number: "T002", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } + updatedWithoutCluster := updatedTree + updatedWithoutCluster.TreeCluster = nil - event := entities.NewEventUpdateTree(&prevTree, &updatedTree) + event := entities.NewEventUpdateTree(&prevWithoutCluster, &updatedWithoutCluster) // when err := svc.HandleUpdateTree(context.Background(), &event) @@ -248,29 +149,16 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { defer cancel() go eventManager.Run(ctx) - tc := entities.TreeCluster{ - ID: 1, - Region: &entities.Region{ - ID: 1, - Name: "Sandberg", - }, - Latitude: utils.P(54.776366336440255), - Longitude: utils.P(9.451084144617182), - } prevTree := entities.Tree{ - ID: 1, - TreeCluster: &tc, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, + TreeCluster: &prevTc, + Latitude: *prevTc.Latitude, + Longitude: *prevTc.Longitude, } updatedTree := entities.Tree{ - ID: 1, - TreeCluster: &tc, - Number: "T002", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, + TreeCluster: &prevTc, + Latitude: *prevTc.Latitude, + Longitude: *prevTc.Longitude, } event := entities.NewEventUpdateTree(&prevTree, &updatedTree) @@ -305,23 +193,6 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { defer cancel() go eventManager.Run(ctx) - prevTc := entities.TreeCluster{ - ID: 1, - Region: &entities.Region{ - ID: 1, - Name: "Sandberg", - }, - Latitude: utils.P(54.776366336440255), - Longitude: utils.P(9.451084144617182), - } - prevTree := entities.Tree{ - ID: 1, - TreeCluster: &prevTc, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } - newTc := entities.TreeCluster{ ID: 2, Region: &entities.Region{ @@ -331,13 +202,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { Latitude: utils.P(54.776366336440255), Longitude: utils.P(9.451084144617182), } - updatedTree := entities.Tree{ - ID: 1, - TreeCluster: &newTc, - Number: "T002", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } + updatedTree.TreeCluster = &newTc event := entities.NewEventUpdateTree(&prevTree, &updatedTree) @@ -367,13 +232,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { t.Run("should listen on create new tree event", func(t *testing.T) { // given eventManager := worker.NewEventManager(entities.EventTypeCreateTree) - newTree := entities.Tree{ - ID: 1, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } - event := entities.NewEventCreateTree(&newTree) + event := entities.NewEventCreateTree(&updatedTree) _, ch, _ := eventManager.Subscribe(entities.EventTypeCreateTree) ctx, cancel := context.WithCancel(context.Background()) @@ -396,19 +255,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { t.Run("should listen on update tree event", func(t *testing.T) { // given eventManager := worker.NewEventManager(entities.EventTypeUpdateTree) - prevTree := entities.Tree{ - ID: 1, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.4510841446171324, - } - newTree := entities.Tree{ - ID: 1, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } - event := entities.NewEventUpdateTree(&prevTree, &newTree) + event := entities.NewEventUpdateTree(&prevTree, &updatedTree) _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTree) ctx, cancel := context.WithCancel(context.Background()) @@ -431,13 +278,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { t.Run("should listen on delete tree event", func(t *testing.T) { // given eventManager := worker.NewEventManager(entities.EventTypeDeleteTree) - newTree := entities.Tree{ - ID: 1, - Number: "T001", - Latitude: 54.776366336440255, - Longitude: 9.451084144617182, - } - event := entities.NewEventDeleteTree(&newTree) + event := entities.NewEventDeleteTree(&updatedTree) _, ch, _ := eventManager.Subscribe(entities.EventTypeDeleteTree) ctx, cancel := context.WithCancel(context.Background()) @@ -457,3 +298,57 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { } }) } + +var prevTc = entities.TreeCluster{ + ID: 1, + Region: &entities.Region{ + ID: 1, + Name: "Sandberg", + }, + Latitude: utils.P(54.776366336440255), + Longitude: utils.P(9.451084144617182), +} + +var prevTree = entities.Tree{ + ID: 1, + TreeCluster: &prevTc, + Number: "T001", + Latitude: 54.776366336440255, + Longitude: 9.451084144617182, + PlantingYear: int32(time.Now().Year() - 2), +} + +var updatedTree = entities.Tree{ + ID: 1, + TreeCluster: &prevTc, + Number: "T001", + Latitude: 54.811733806341856, + Longitude: 9.482958846410169, + PlantingYear: int32(time.Now().Year() - 2), + Sensor: &entities.Sensor{ + ID: "sensor-1", + }, +} + +var updatedTc = entities.TreeCluster{ + ID: 1, + Region: &entities.Region{ + ID: 2, + Name: "Mürwik", + }, + Latitude: utils.P(54.811733806341856), + Longitude: utils.P(9.482958846410169), +} + +var allLatestSensorData = []*entities.SensorData{ + { + SensorID: "sensor-1", + Data: &entities.MqttPayload{ + Watermarks: []entities.Watermark{ + {Centibar: 61, Depth: 30}, + {Centibar: 24, Depth: 60}, + {Centibar: 23, Depth: 90}, + }, + }, + }, +} From bbc17b2d99a54ca610e31dea910ae3393d0f9ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 14:45:22 +0100 Subject: [PATCH 10/17] feat: set watering status of tree cluster on update and create --- internal/service/domain/treecluster/handle_tree_update.go | 2 +- internal/service/domain/treecluster/treecluster.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/service/domain/treecluster/handle_tree_update.go b/internal/service/domain/treecluster/handle_tree_update.go index 128bd089..4472db94 100644 --- a/internal/service/domain/treecluster/handle_tree_update.go +++ b/internal/service/domain/treecluster/handle_tree_update.go @@ -67,7 +67,7 @@ func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *en return nil } - wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree) + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree.TreeCluster.ID) if err != nil { slog.Error("could not update watering status", "error", err) } diff --git a/internal/service/domain/treecluster/treecluster.go b/internal/service/domain/treecluster/treecluster.go index 3994ae95..362db0b3 100644 --- a/internal/service/domain/treecluster/treecluster.go +++ b/internal/service/domain/treecluster/treecluster.go @@ -232,7 +232,12 @@ func (s *TreeClusterService) Ready() bool { // otherwise the center point of the tree cluster cannot be set func (s *TreeClusterService) updateTreeClusterPosition(ctx context.Context, id int32) error { log := logger.GetLogger(ctx) - err := s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, id) + if err != nil { + slog.Error("could not update watering status", "error", err) + } + + err = s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { lat, long, region, err := s.getUpdatedLatLong(ctx, tc) if err != nil { log.Debug("cancel transaction on updateting tree cluster position due to error", "error", err, "cluster_id", id) From 4b90394e6eb0cd933b787100ea4dca89c137bcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 19 Jan 2025 12:22:05 +0100 Subject: [PATCH 11/17] fix: update treecluster service tests --- .../treecluster/handle_tree_update_test.go | 40 ++-- .../domain/treecluster/treecluster_test.go | 204 +++++++++++------- 2 files changed, 141 insertions(+), 103 deletions(-) diff --git a/internal/service/domain/treecluster/handle_tree_update_test.go b/internal/service/domain/treecluster/handle_tree_update_test.go index 04338e15..0ae7965e 100644 --- a/internal/service/domain/treecluster/handle_tree_update_test.go +++ b/internal/service/domain/treecluster/handle_tree_update_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/green-ecolution/green-ecolution-backend/internal/entities" + "github.com/green-ecolution/green-ecolution-backend/internal/service" "github.com/green-ecolution/green-ecolution-backend/internal/storage" storageMock "github.com/green-ecolution/green-ecolution-backend/internal/storage/_mock" "github.com/green-ecolution/green-ecolution-backend/internal/utils" @@ -16,11 +17,7 @@ import ( func TestTreeClusterService_HandleUpdateTree(t *testing.T) { t.Run("should update tree cluster lat, long, region, watering status and send treecluster update event", func(t *testing.T) { - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - treeRepo := storageMock.NewMockTreeRepository(t) - regionRepo := storageMock.NewMockRegionRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) - svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + clusterRepo, treeRepo, _, eventManager, svc := setupTest(t) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTreeCluster) @@ -58,11 +55,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { }) t.Run("should update tree cluster watering status to unkown and send treecluster update event", func(t *testing.T) { - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - treeRepo := storageMock.NewMockTreeRepository(t) - regionRepo := storageMock.NewMockRegionRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) - svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + clusterRepo, _, _, eventManager, svc := setupTest(t) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTreeCluster) @@ -99,11 +92,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { }) t.Run("should not update tree cluster if treeclusters in event are nil", func(t *testing.T) { - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - treeRepo := storageMock.NewMockTreeRepository(t) - regionRepo := storageMock.NewMockRegionRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) - svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + clusterRepo, _, regionRepo, eventManager, svc := setupTest(t) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTreeCluster) @@ -137,11 +126,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { }) t.Run("should not update tree cluster if tree has not changed location", func(t *testing.T) { - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - treeRepo := storageMock.NewMockTreeRepository(t) - regionRepo := storageMock.NewMockRegionRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) - svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + clusterRepo, _, regionRepo, eventManager, svc := setupTest(t) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTreeCluster) @@ -181,11 +166,7 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { }) t.Run("should update if tree location is equal but tree has changed treecluster", func(t *testing.T) { - clusterRepo := storageMock.NewMockTreeClusterRepository(t) - treeRepo := storageMock.NewMockTreeRepository(t) - regionRepo := storageMock.NewMockRegionRepository(t) - eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) - svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + clusterRepo, _, regionRepo, eventManager, svc := setupTest(t) // event _, ch, _ := eventManager.Subscribe(entities.EventTypeUpdateTreeCluster) @@ -299,6 +280,15 @@ func TestTreeClusterService_HandleUpdateTree(t *testing.T) { }) } +func setupTest(t *testing.T) (*storageMock.MockTreeClusterRepository, *storageMock.MockTreeRepository, *storageMock.MockRegionRepository, *worker.EventManager, service.TreeClusterService) { + clusterRepo := storageMock.NewMockTreeClusterRepository(t) + treeRepo := storageMock.NewMockTreeRepository(t) + regionRepo := storageMock.NewMockRegionRepository(t) + eventManager := worker.NewEventManager(entities.EventTypeUpdateTreeCluster) + svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, eventManager) + return clusterRepo, treeRepo, regionRepo, eventManager, svc +} + var prevTc = entities.TreeCluster{ ID: 1, Region: &entities.Region{ diff --git a/internal/service/domain/treecluster/treecluster_test.go b/internal/service/domain/treecluster/treecluster_test.go index 19651a65..82853787 100644 --- a/internal/service/domain/treecluster/treecluster_test.go +++ b/internal/service/domain/treecluster/treecluster_test.go @@ -26,7 +26,7 @@ func TestTreeClusterService_GetAll(t *testing.T) { regionRepo := storageMock.NewMockRegionRepository(t) svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) - expectedClusters := getTestTreeClusters() + expectedClusters := testClusters clusterRepo.EXPECT().GetAll(ctx).Return(expectedClusters, nil) // when @@ -83,7 +83,7 @@ func TestTreeClusterService_GetByID(t *testing.T) { t.Run("should return tree cluster when found", func(t *testing.T) { id := int32(1) - expectedCluster := getTestTreeClusters()[0] + expectedCluster := testClusters[0] clusterRepo.EXPECT().GetByID(ctx, id).Return(expectedCluster, nil) // when @@ -125,19 +125,28 @@ func TestTreeClusterService_Create(t *testing.T) { regionRepo := storageMock.NewMockRegionRepository(t) svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) - expectedCluster := getTestTreeClusters()[0] - expectedTrees := getTestTrees() + expectedCluster := testClusters[0] treeRepo.EXPECT().GetTreesByIDs( ctx, []int32{1, 2}, - ).Return(expectedTrees, nil) + ).Return(testTrees, nil) clusterRepo.EXPECT().Create( ctx, mock.Anything, ).Return(expectedCluster, nil) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID( + ctx, + int32(1), + ).Return(allLatestSensorData, nil) + + treeRepo.EXPECT().GetBySensorIDs( + ctx, + "sensor-1", + ).Return(testTrees, nil) + clusterRepo.EXPECT().Update( ctx, expectedCluster.ID, @@ -166,7 +175,7 @@ func TestTreeClusterService_Create(t *testing.T) { TreeIDs: []*int32{}, } - expectedCluster := getTestTreeClusters()[1] + expectedCluster := testClusters[1] treeRepo.EXPECT().GetTreesByIDs( ctx, @@ -178,6 +187,11 @@ func TestTreeClusterService_Create(t *testing.T) { mock.Anything, ).Return(expectedCluster, nil) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID( + ctx, + int32(2), + ).Return(nil, storage.ErrSensorNotFound) + clusterRepo.EXPECT().Update( ctx, expectedCluster.ID, @@ -221,7 +235,7 @@ func TestTreeClusterService_Create(t *testing.T) { svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) expectedErr := errors.New("Failed to create cluster") - expectedTrees := getTestTrees() + expectedTrees := testTrees treeRepo.EXPECT().GetTreesByIDs( ctx, @@ -248,9 +262,9 @@ func TestTreeClusterService_Create(t *testing.T) { regionRepo := storageMock.NewMockRegionRepository(t) svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) - expectedCluster := getTestTreeClusters()[0] + expectedCluster := testClusters[0] expectedErr := errors.New("Failed to create cluster") - expectedTrees := getTestTrees() + expectedTrees := testTrees treeRepo.EXPECT().GetTreesByIDs( ctx, @@ -262,6 +276,16 @@ func TestTreeClusterService_Create(t *testing.T) { mock.Anything, ).Return(expectedCluster, nil) + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID( + ctx, + int32(1), + ).Return(allLatestSensorData, nil) + + treeRepo.EXPECT().GetBySensorIDs( + ctx, + "sensor-1", + ).Return(testTrees, nil) + clusterRepo.EXPECT().Update( ctx, expectedCluster.ID, @@ -319,8 +343,8 @@ func TestTreeClusterService_Update(t *testing.T) { regionRepo := storageMock.NewMockRegionRepository(t) svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) - expectedCluster := getTestTreeClusters()[0] - expectedTrees := getTestTrees() + expectedCluster := testClusters[0] + expectedTrees := testTrees treeRepo.EXPECT().GetTreesByIDs( ctx, @@ -328,6 +352,17 @@ func TestTreeClusterService_Update(t *testing.T) { ).Return(expectedTrees, nil) clusterRepo.EXPECT().GetByID(ctx, clusterID).Return(expectedCluster, nil) + + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID( + ctx, + int32(1), + ).Return(allLatestSensorData, nil) + + treeRepo.EXPECT().GetBySensorIDs( + ctx, + "sensor-1", + ).Return(testTrees, nil) + clusterRepo.EXPECT().Update( ctx, clusterID, @@ -356,7 +391,7 @@ func TestTreeClusterService_Update(t *testing.T) { TreeIDs: []*int32{}, } - expectedCluster := getTestTreeClusters()[1] + expectedCluster := testClusters[1] treeRepo.EXPECT().GetTreesByIDs( ctx, @@ -364,6 +399,12 @@ func TestTreeClusterService_Update(t *testing.T) { ).Return(nil, nil) clusterRepo.EXPECT().GetByID(ctx, expectedCluster.ID).Return(expectedCluster, nil) + + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID( + ctx, + int32(2), + ).Return(nil, storage.ErrSensorNotFound) + clusterRepo.EXPECT().Update( ctx, expectedCluster.ID, @@ -405,7 +446,7 @@ func TestTreeClusterService_Update(t *testing.T) { svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) expectedErr := errors.New("failed to update cluster") - expectedTrees := getTestTrees() + expectedTrees := testTrees treeRepo.EXPECT().GetTreesByIDs( ctx, @@ -434,12 +475,10 @@ func TestTreeClusterService_Update(t *testing.T) { regionRepo := storageMock.NewMockRegionRepository(t) svc := NewTreeClusterService(clusterRepo, treeRepo, regionRepo, globalEventManager) - expectedTrees := getTestTrees() - treeRepo.EXPECT().GetTreesByIDs( ctx, []int32{1, 2}, - ).Return(expectedTrees, nil) + ).Return(testTrees, nil) clusterRepo.EXPECT().GetByID(ctx, clusterID).Return(nil, nil) clusterRepo.EXPECT().Update( @@ -488,7 +527,7 @@ func TestTreeClusterService_EventSystem(t *testing.T) { treeRepo := storageMock.NewMockTreeRepository(t) regionRepo := storageMock.NewMockRegionRepository(t) - clusters := getTestTreeClusters() + clusters := testClusters prevCluster := *clusters[1] updatedClusterEmptyTrees := &entities.TreeClusterUpdate{ Name: "Cluster 1", @@ -513,6 +552,17 @@ func TestTreeClusterService_EventSystem(t *testing.T) { ).Return(nil, nil) clusterRepo.EXPECT().GetByID(ctx, expectedCluster.ID).Return(&expectedCluster, nil) + + clusterRepo.EXPECT().GetAllLatestSensorDataByClusterID( + ctx, + int32(2), + ).Return(allLatestSensorData, nil) + + treeRepo.EXPECT().GetBySensorIDs( + ctx, + "sensor-1", + ).Return(testTrees, nil) + clusterRepo.EXPECT().Update( ctx, expectedCluster.ID, @@ -552,7 +602,7 @@ func TestTreeClusterService_Delete(t *testing.T) { t.Run("should successfully delete a tree cluster", func(t *testing.T) { id := int32(1) - clusterRepo.EXPECT().GetByID(ctx, id).Return(getTestTreeClusters()[0], nil) + clusterRepo.EXPECT().GetByID(ctx, id).Return(testClusters[0], nil) treeRepo.EXPECT().UnlinkTreeClusterID(ctx, id).Return(nil) clusterRepo.EXPECT().Delete(ctx, id).Return(nil) @@ -582,7 +632,7 @@ func TestTreeClusterService_Delete(t *testing.T) { id := int32(3) expectedErr := errors.New("failed to unlink treecluster ID") - clusterRepo.EXPECT().GetByID(ctx, id).Return(getTestTreeClusters()[0], nil) + clusterRepo.EXPECT().GetByID(ctx, id).Return(testClusters[0], nil) treeRepo.EXPECT().UnlinkTreeClusterID(ctx, id).Return(expectedErr) // when @@ -597,7 +647,7 @@ func TestTreeClusterService_Delete(t *testing.T) { id := int32(4) expectedErr := errors.New("failed to delete") - clusterRepo.EXPECT().GetByID(ctx, id).Return(getTestTreeClusters()[0], nil) + clusterRepo.EXPECT().GetByID(ctx, id).Return(testClusters[0], nil) treeRepo.EXPECT().UnlinkTreeClusterID(ctx, id).Return(nil) clusterRepo.EXPECT().Delete(ctx, id).Return(expectedErr) @@ -635,66 +685,64 @@ func TestReady(t *testing.T) { }) } -func getTestTreeClusters() []*entities.TreeCluster { - now := time.Now() - - return []*entities.TreeCluster{ - { - ID: 1, - CreatedAt: now, - UpdatedAt: now, - Name: "Cluster 1", - Address: "123 Main St", - Description: "Test description", - SoilCondition: entities.TreeSoilConditionLehmig, - Archived: false, - Latitude: utils.P(9.446741), - Longitude: utils.P(54.801539), - Trees: getTestTrees(), - }, - { - ID: 2, - CreatedAt: now, - UpdatedAt: now, - Name: "Cluster 2", - Address: "456 Second St", - Description: "Test description", - SoilCondition: entities.TreeSoilConditionSandig, - Archived: false, - Latitude: nil, - Longitude: nil, - Trees: []*entities.Tree{}, - }, - } +var testClusters = []*entities.TreeCluster{ + { + ID: 1, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Name: "Cluster 1", + Address: "123 Main St", + Description: "Test description", + SoilCondition: entities.TreeSoilConditionLehmig, + Archived: false, + Latitude: utils.P(9.446741), + Longitude: utils.P(54.801539), + Trees: testTrees, + }, + { + ID: 2, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Name: "Cluster 2", + Address: "456 Second St", + Description: "Test description", + SoilCondition: entities.TreeSoilConditionSandig, + Archived: false, + Latitude: nil, + Longitude: nil, + Trees: testTrees, + }, } -func getTestTrees() []*entities.Tree { - now := time.Now() - - return []*entities.Tree{ - { - ID: 1, - CreatedAt: now, - UpdatedAt: now, - Species: "Oak", - Number: "T001", - Latitude: 9.446741, - Longitude: 54.801539, - Description: "A mature oak tree", - PlantingYear: 2023, - Readonly: true, +var testTrees = []*entities.Tree{ + { + ID: 1, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Species: "Oak", + Number: "T001", + Latitude: 9.446741, + Longitude: 54.801539, + Description: "A mature oak tree", + PlantingYear: 2023, + Readonly: true, + Sensor: &entities.Sensor{ + ID: "sensor-1", }, - { - ID: 2, - CreatedAt: now, - UpdatedAt: now, - Species: "Pine", - Number: "T002", - Latitude: 9.446700, - Longitude: 54.801510, - Description: "A young pine tree", - PlantingYear: 2023, - Readonly: true, + }, + { + ID: 2, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Species: "Pine", + Number: "T002", + Latitude: 9.446700, + Longitude: 54.801510, + Description: "A young pine tree", + PlantingYear: 2023, + Readonly: true, + Sensor: &entities.Sensor{ + ID: "sensor-2", }, - } + }, } From 065c6a76eda50cdd54e58aac00bd85036ef8919e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 19 Jan 2025 13:05:23 +0100 Subject: [PATCH 12/17] chore: satify linter --- internal/service/domain/treecluster/handle_tree_update_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/domain/treecluster/handle_tree_update_test.go b/internal/service/domain/treecluster/handle_tree_update_test.go index 0ae7965e..cd6d0f77 100644 --- a/internal/service/domain/treecluster/handle_tree_update_test.go +++ b/internal/service/domain/treecluster/handle_tree_update_test.go @@ -15,6 +15,7 @@ import ( mock "github.com/stretchr/testify/mock" ) +//nolint:gocyclo // function handles multiple test cases and complex event logic, which requires higher complexity to cover all scenarios. func TestTreeClusterService_HandleUpdateTree(t *testing.T) { t.Run("should update tree cluster lat, long, region, watering status and send treecluster update event", func(t *testing.T) { clusterRepo, treeRepo, _, eventManager, svc := setupTest(t) From a9d783ce02eab0014af766c1376c316f7226b2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 14:48:43 +0100 Subject: [PATCH 13/17] fix: add missing context to calculate watering status function --- internal/service/domain/tree/tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/domain/tree/tree.go b/internal/service/domain/tree/tree.go index 605337fc..ce670511 100644 --- a/internal/service/domain/tree/tree.go +++ b/internal/service/domain/tree/tree.go @@ -128,7 +128,7 @@ func (s *TreeService) Create(ctx context.Context, treeCreate *entities.TreeCreat fn = append(fn, tree.WithSensor(sensor)) if sensor.LatestData != nil && sensor.LatestData.Data != nil && len(sensor.LatestData.Data.Watermarks) > 0 { - status := utils.CalculateWateringStatus(treeCreate.PlantingYear, sensor.LatestData.Data.Watermarks) + status := utils.CalculateWateringStatus(ctx, treeCreate.PlantingYear, sensor.LatestData.Data.Watermarks) fn = append(fn, tree.WithWateringStatus(status)) } } @@ -210,7 +210,7 @@ func (s *TreeService) Update(ctx context.Context, id int32, tu *entities.TreeUpd fn = append(fn, tree.WithSensor(sensor)) if sensor.LatestData != nil && sensor.LatestData.Data != nil && len(sensor.LatestData.Data.Watermarks) > 0 { - status := utils.CalculateWateringStatus(tu.PlantingYear, sensor.LatestData.Data.Watermarks) + status := utils.CalculateWateringStatus(ctx, tu.PlantingYear, sensor.LatestData.Data.Watermarks) fn = append(fn, tree.WithWateringStatus(status)) } } else { From 581d2f65733941d713b048b822c4db820eeb5876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 14:55:41 +0100 Subject: [PATCH 14/17] fix: update missing variables due to wrong rebase --- .../domain/treecluster/handle_new_sensor_data.go | 8 ++++---- .../domain/treecluster/handle_tree_update.go | 2 +- internal/service/domain/treecluster/treecluster.go | 14 ++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/internal/service/domain/treecluster/handle_new_sensor_data.go b/internal/service/domain/treecluster/handle_new_sensor_data.go index 9f04e555..8803f122 100644 --- a/internal/service/domain/treecluster/handle_new_sensor_data.go +++ b/internal/service/domain/treecluster/handle_new_sensor_data.go @@ -31,7 +31,7 @@ func (s *TreeClusterService) HandleNewSensorData(ctx context.Context, event *ent return nil } - wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree) + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree.TreeCluster.ID) if err != nil { return nil } @@ -52,11 +52,11 @@ func (s *TreeClusterService) HandleNewSensorData(ctx context.Context, event *ent return nil } -func (s *TreeClusterService) getWateringStatusOfTreeCluster(ctx context.Context, tree *entities.Tree) (entities.WateringStatus, error) { +func (s *TreeClusterService) getWateringStatusOfTreeCluster(ctx context.Context, clusterID int32) (entities.WateringStatus, error) { log := logger.GetLogger(ctx) - sensorData, err := s.treeClusterRepo.GetAllLatestSensorDataByClusterID(ctx, tree.TreeCluster.ID) + sensorData, err := s.treeClusterRepo.GetAllLatestSensorDataByClusterID(ctx, clusterID) if err != nil { - log.Error("failed to get latest sensor data", "cluster_id", tree.TreeCluster.ID, "err", err) + log.Error("failed to get latest sensor data", "cluster_id", clusterID, "err", err) return entities.WateringStatusUnknown, errors.New("failed to get latest sensor data") } diff --git a/internal/service/domain/treecluster/handle_tree_update.go b/internal/service/domain/treecluster/handle_tree_update.go index 4472db94..4afdc263 100644 --- a/internal/service/domain/treecluster/handle_tree_update.go +++ b/internal/service/domain/treecluster/handle_tree_update.go @@ -69,7 +69,7 @@ func (s *TreeClusterService) handleTreeClusterUpdate(ctx context.Context, tc *en wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree.TreeCluster.ID) if err != nil { - slog.Error("could not update watering status", "error", err) + log.Error("could not update watering status", "error", err) } updateFn := func(tc *entities.TreeCluster) (bool, error) { diff --git a/internal/service/domain/treecluster/treecluster.go b/internal/service/domain/treecluster/treecluster.go index 362db0b3..056348a2 100644 --- a/internal/service/domain/treecluster/treecluster.go +++ b/internal/service/domain/treecluster/treecluster.go @@ -167,12 +167,18 @@ func (s *TreeClusterService) Update(ctx context.Context, id int32, tcUpdate *dom return nil, service.MapError(ctx, err, service.ErrorLogEntityNotFound) } + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, id) + if err != nil { + slog.Error("could not update watering status", "error", err) + } + err = s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { tc.Trees = trees tc.Name = tcUpdate.Name tc.Address = tcUpdate.Address tc.Description = tcUpdate.Description tc.SoilCondition = tcUpdate.SoilCondition + tc.WateringStatus = wateringStatus log.Debug("updating tree cluster with following attributes", "cluster_id", id, @@ -180,6 +186,7 @@ func (s *TreeClusterService) Update(ctx context.Context, id int32, tcUpdate *dom "address", tcUpdate.Address, "description", tcUpdate.Description, "soil_condition", tcUpdate.SoilCondition, + "watering_status", wateringStatus, ) return true, nil @@ -232,12 +239,7 @@ func (s *TreeClusterService) Ready() bool { // otherwise the center point of the tree cluster cannot be set func (s *TreeClusterService) updateTreeClusterPosition(ctx context.Context, id int32) error { log := logger.GetLogger(ctx) - wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, id) - if err != nil { - slog.Error("could not update watering status", "error", err) - } - - err = s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { + err := s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { lat, long, region, err := s.getUpdatedLatLong(ctx, tc) if err != nil { log.Debug("cancel transaction on updateting tree cluster position due to error", "error", err, "cluster_id", id) From a51c807f4179778fb260c62ca2116e09f7cecfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 15:24:45 +0100 Subject: [PATCH 15/17] feat: update watering status on update tree cluster position --- .../service/domain/treecluster/treecluster.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/service/domain/treecluster/treecluster.go b/internal/service/domain/treecluster/treecluster.go index 056348a2..66f36359 100644 --- a/internal/service/domain/treecluster/treecluster.go +++ b/internal/service/domain/treecluster/treecluster.go @@ -167,18 +167,12 @@ func (s *TreeClusterService) Update(ctx context.Context, id int32, tcUpdate *dom return nil, service.MapError(ctx, err, service.ErrorLogEntityNotFound) } - wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, id) - if err != nil { - slog.Error("could not update watering status", "error", err) - } - err = s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { tc.Trees = trees tc.Name = tcUpdate.Name tc.Address = tcUpdate.Address tc.Description = tcUpdate.Description tc.SoilCondition = tcUpdate.SoilCondition - tc.WateringStatus = wateringStatus log.Debug("updating tree cluster with following attributes", "cluster_id", id, @@ -186,7 +180,6 @@ func (s *TreeClusterService) Update(ctx context.Context, id int32, tcUpdate *dom "address", tcUpdate.Address, "description", tcUpdate.Description, "soil_condition", tcUpdate.SoilCondition, - "watering_status", wateringStatus, ) return true, nil @@ -239,7 +232,15 @@ func (s *TreeClusterService) Ready() bool { // otherwise the center point of the tree cluster cannot be set func (s *TreeClusterService) updateTreeClusterPosition(ctx context.Context, id int32) error { log := logger.GetLogger(ctx) - err := s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { + wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, id) + if err != nil { + slog.Error("could not update watering status", "error", err) + } + + fmt.Println("___________WATERINGSTATUS__________") + fmt.Println(wateringStatus) + + err = s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { lat, long, region, err := s.getUpdatedLatLong(ctx, tc) if err != nil { log.Debug("cancel transaction on updateting tree cluster position due to error", "error", err, "cluster_id", id) @@ -250,6 +251,7 @@ func (s *TreeClusterService) updateTreeClusterPosition(ctx context.Context, id i tc.Latitude = lat tc.Longitude = long tc.Region = region + tc.WateringStatus = wateringStatus log.Info("update tree cluster position due to changed trees inside the tree cluster", "cluster_id", id) log.Debug("detailed updated tree cluster position informations", "cluster_id", id, From 7b72813a6f9b2672dbbe40c7d289fabdfaca9a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Sun, 26 Jan 2025 15:35:05 +0100 Subject: [PATCH 16/17] fix: return error on wrong update --- internal/service/domain/tree/handle_new_sensor_data.go | 1 + internal/service/domain/tree/handle_new_sensor_data_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/domain/tree/handle_new_sensor_data.go b/internal/service/domain/tree/handle_new_sensor_data.go index 87173c9d..69f7974d 100644 --- a/internal/service/domain/tree/handle_new_sensor_data.go +++ b/internal/service/domain/tree/handle_new_sensor_data.go @@ -28,6 +28,7 @@ func (s *TreeService) HandleNewSensorData(ctx context.Context, event *entities.E newTree, err := s.treeRepo.Update(ctx, t.ID, tree.WithWateringStatus(status)) if err != nil { log.Error("failed to update tree with new watering status", "tree_id", t.ID, "watering_status", status, "err", err) + return err } slog.Info("updating tree watering status", "prev_status", t.WateringStatus, "new_status", status) diff --git a/internal/service/domain/tree/handle_new_sensor_data_test.go b/internal/service/domain/tree/handle_new_sensor_data_test.go index 8a6ebb08..6e0fc84a 100644 --- a/internal/service/domain/tree/handle_new_sensor_data_test.go +++ b/internal/service/domain/tree/handle_new_sensor_data_test.go @@ -151,7 +151,7 @@ func TestTreeService_HandleNewSensorData(t *testing.T) { err := svc.HandleNewSensorData(context.Background(), &event) // then - assert.NoError(t, err) + assert.ErrorIs(t, err, storage.ErrTreeNotFound) select { case <-ch: t.Fatal("event was received. It should not have been sent") From 1e7cc346e4e771f6367716e83404b819d7fc9d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorien=20Gr=C3=B6nwald?= Date: Mon, 27 Jan 2025 20:45:05 +0100 Subject: [PATCH 17/17] chore: update logging and remove debug statements --- internal/service/domain/tree/handle_new_sensor_data.go | 1 + .../service/domain/treecluster/handle_new_sensor_data.go | 2 ++ internal/service/domain/treecluster/treecluster.go | 5 +---- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/domain/tree/handle_new_sensor_data.go b/internal/service/domain/tree/handle_new_sensor_data.go index 69f7974d..a4cb3eb7 100644 --- a/internal/service/domain/tree/handle_new_sensor_data.go +++ b/internal/service/domain/tree/handle_new_sensor_data.go @@ -22,6 +22,7 @@ func (s *TreeService) HandleNewSensorData(ctx context.Context, event *entities.E status := utils.CalculateWateringStatus(ctx, t.PlantingYear, event.New.Data.Watermarks) if status == t.WateringStatus { + log.Debug("sensor status has not changed", "sensor_status", status) return nil } diff --git a/internal/service/domain/treecluster/handle_new_sensor_data.go b/internal/service/domain/treecluster/handle_new_sensor_data.go index 8803f122..d6dce360 100644 --- a/internal/service/domain/treecluster/handle_new_sensor_data.go +++ b/internal/service/domain/treecluster/handle_new_sensor_data.go @@ -33,10 +33,12 @@ func (s *TreeClusterService) HandleNewSensorData(ctx context.Context, event *ent wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, tree.TreeCluster.ID) if err != nil { + log.Error("error while calculating watering status of tree cluster", "error", err) return nil } if wateringStatus == tree.TreeCluster.WateringStatus { + log.Debug("watering status has not changed", "watering_status", wateringStatus) return nil } diff --git a/internal/service/domain/treecluster/treecluster.go b/internal/service/domain/treecluster/treecluster.go index 66f36359..2b7e54e2 100644 --- a/internal/service/domain/treecluster/treecluster.go +++ b/internal/service/domain/treecluster/treecluster.go @@ -234,12 +234,9 @@ func (s *TreeClusterService) updateTreeClusterPosition(ctx context.Context, id i log := logger.GetLogger(ctx) wateringStatus, err := s.getWateringStatusOfTreeCluster(ctx, id) if err != nil { - slog.Error("could not update watering status", "error", err) + log.Error("could not update watering status", "error", err) } - fmt.Println("___________WATERINGSTATUS__________") - fmt.Println(wateringStatus) - err = s.treeClusterRepo.Update(ctx, id, func(tc *domain.TreeCluster) (bool, error) { lat, long, region, err := s.getUpdatedLatLong(ctx, tc) if err != nil {