package handler_test import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "bugulma/backend/internal/testutils" "github.com/gin-gonic/gin" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "gorm.io/datatypes" "gorm.io/gorm" "bugulma/backend/internal/domain" "bugulma/backend/internal/handler" "bugulma/backend/internal/repository" "bugulma/backend/internal/service" ) var _ = Describe("ResourceFlowHandler", func() { var ( resourceHandler *handler.ResourceFlowHandler resourceService *service.ResourceFlowService resourceRepo domain.ResourceFlowRepository organizationRepo domain.OrganizationRepository siteRepo domain.SiteRepository router *gin.Engine db *gorm.DB ) BeforeEach(func() { gin.SetMode(gin.TestMode) // Setup PostgreSQL test database using pgtestdb db = testutils.SetupTestDBForGinkgo(GinkgoT()) resourceRepo = repository.NewResourceFlowRepository(db) organizationRepo = repository.NewOrganizationRepository(db) siteRepo = repository.NewSiteRepository(db) resourceService = service.NewResourceFlowService(resourceRepo, nil) resourceHandler = handler.NewResourceFlowHandler(resourceService) router = gin.New() router.POST("/resources", resourceHandler.Create) router.GET("/resources/:id", resourceHandler.GetByID) router.PUT("/resources/:id", resourceHandler.Update) router.GET("/sites/:siteId/resources", resourceHandler.GetBySite) router.GET("/organizations/:organizationId/resources", resourceHandler.GetByOrganization) router.DELETE("/resources/:id", resourceHandler.Delete) }) AfterEach(func() { // pgtestdb automatically cleans up the database after each test }) Describe("Create", func() { It("should create a resource flow", func() { // Create organization and site first (required for foreign keys) org := &domain.Organization{ID: "org-1", Name: "Org 1"} site := &domain.Site{ID: "site-1", Name: "Site 1", Latitude: 52.52, Longitude: 13.405, OwnerOrganizationID: "org-1"} Expect(organizationRepo.Create(context.TODO(), org)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site)).To(Succeed()) reqBody := handler.CreateResourceFlowRequest{ OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: domain.Quantity{ Amount: 100, Unit: "kWh", }, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", "/resources", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) Expect(w.Code).To(Equal(http.StatusCreated)) var resp domain.ResourceFlow err := json.Unmarshal(w.Body.Bytes(), &resp) Expect(err).NotTo(HaveOccurred()) Expect(resp.OrganizationID).To(Equal("org-1")) Expect(resp.ID).NotTo(BeEmpty()) }) }) Describe("GetByID", func() { It("should return resource flow by ID", func() { // Create organization and site first org := &domain.Organization{ID: "org-1", Name: "Org 1"} site := &domain.Site{ID: "site-1", Name: "Site 1", Latitude: 52.52, Longitude: 13.405, OwnerOrganizationID: "org-1"} Expect(organizationRepo.Create(context.TODO(), org)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site)).To(Succeed()) rf := &domain.ResourceFlow{ ID: "rf-1", OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 100, "unit": "kWh"}`), } Expect(resourceRepo.Create(context.TODO(), rf)).To(Succeed()) req, _ := http.NewRequest("GET", "/resources/rf-1", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) Expect(w.Code).To(Equal(http.StatusOK)) var resp domain.ResourceFlow err := json.Unmarshal(w.Body.Bytes(), &resp) Expect(err).NotTo(HaveOccurred()) Expect(resp.ID).To(Equal("rf-1")) }) }) Describe("GetBySite", func() { It("should return resources for a site", func() { // Create organizations and sites first org1 := &domain.Organization{ID: "org-1", Name: "Org 1"} org2 := &domain.Organization{ID: "org-2", Name: "Org 2"} site1 := &domain.Site{ID: "site-1", Name: "Site 1", Latitude: 52.52, Longitude: 13.405, OwnerOrganizationID: "org-1"} site2 := &domain.Site{ID: "site-2", Name: "Site 2", Latitude: 52.53, Longitude: 13.406, OwnerOrganizationID: "org-2"} Expect(organizationRepo.Create(context.TODO(), org1)).To(Succeed()) Expect(organizationRepo.Create(context.TODO(), org2)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site1)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site2)).To(Succeed()) Expect(resourceRepo.Create(context.TODO(), &domain.ResourceFlow{ID: "rf-1", OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 100, "unit": "kWh"}`)})).To(Succeed()) Expect(resourceRepo.Create(context.TODO(), &domain.ResourceFlow{ID: "rf-2", OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeWater, Quantity: datatypes.JSON(`{"amount": 50, "unit": "liters"}`)})).To(Succeed()) Expect(resourceRepo.Create(context.TODO(), &domain.ResourceFlow{ID: "rf-3", OrganizationID: "org-2", SiteID: "site-2", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 200, "unit": "kWh"}`)})).To(Succeed()) req, _ := http.NewRequest("GET", "/sites/site-1/resources", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) Expect(w.Code).To(Equal(http.StatusOK)) var resp []domain.ResourceFlow err := json.Unmarshal(w.Body.Bytes(), &resp) Expect(err).NotTo(HaveOccurred()) Expect(resp).To(HaveLen(2)) }) }) Describe("Update", func() { It("should update a resource flow", func() { // Create organization and site first org := &domain.Organization{ID: "org-1", Name: "Org 1"} site := &domain.Site{ID: "site-1", Name: "Site 1", Latitude: 52.52, Longitude: 13.405, OwnerOrganizationID: "org-1"} Expect(organizationRepo.Create(context.TODO(), org)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site)).To(Succeed()) rf := &domain.ResourceFlow{ ID: "rf-1", OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 100, "unit": "kWh"}`), } Expect(resourceRepo.Create(context.TODO(), rf)).To(Succeed()) updateReq := handler.CreateResourceFlowRequest{ OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: domain.Quantity{ Amount: 150, Unit: "kWh", }, } jsonData, _ := json.Marshal(updateReq) req, _ := http.NewRequest("PUT", "/resources/rf-1", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) Expect(w.Code).To(Equal(http.StatusOK)) // Verify update updated, err := resourceRepo.GetByID(context.Background(), "rf-1") Expect(err).NotTo(HaveOccurred()) Expect(updated.Quantity).NotTo(BeNil()) }) }) Describe("GetByOrganization", func() { It("should return resources for an organization", func() { // Create organizations and sites first org1 := &domain.Organization{ID: "org-1", Name: "Org 1"} org2 := &domain.Organization{ID: "org-2", Name: "Org 2"} site1 := &domain.Site{ID: "site-1", Name: "Site 1", Latitude: 52.52, Longitude: 13.405, OwnerOrganizationID: "org-1"} site2 := &domain.Site{ID: "site-2", Name: "Site 2", Latitude: 52.53, Longitude: 13.406, OwnerOrganizationID: "org-1"} Expect(organizationRepo.Create(context.TODO(), org1)).To(Succeed()) Expect(organizationRepo.Create(context.TODO(), org2)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site1)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site2)).To(Succeed()) Expect(resourceRepo.Create(context.TODO(), &domain.ResourceFlow{ID: "rf-1", OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 100, "unit": "kWh"}`)})).To(Succeed()) Expect(resourceRepo.Create(context.TODO(), &domain.ResourceFlow{ID: "rf-2", OrganizationID: "org-1", SiteID: "site-2", Direction: domain.DirectionOutput, Type: domain.TypeWater, Quantity: datatypes.JSON(`{"amount": 50, "unit": "liters"}`)})).To(Succeed()) Expect(resourceRepo.Create(context.TODO(), &domain.ResourceFlow{ID: "rf-3", OrganizationID: "org-2", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 200, "unit": "kWh"}`)})).To(Succeed()) req, _ := http.NewRequest("GET", "/organizations/org-1/resources", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) Expect(w.Code).To(Equal(http.StatusOK)) var resp []domain.ResourceFlow err := json.Unmarshal(w.Body.Bytes(), &resp) Expect(err).NotTo(HaveOccurred()) Expect(resp).To(HaveLen(2)) }) }) Describe("Delete", func() { It("should delete a resource flow", func() { // Create organization and site first org := &domain.Organization{ID: "org-1", Name: "Org 1"} site := &domain.Site{ID: "site-1", Name: "Site 1", Latitude: 52.52, Longitude: 13.405, OwnerOrganizationID: "org-1"} Expect(organizationRepo.Create(context.TODO(), org)).To(Succeed()) Expect(siteRepo.Create(context.TODO(), site)).To(Succeed()) rf := &domain.ResourceFlow{ ID: "rf-1", OrganizationID: "org-1", SiteID: "site-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 100, "unit": "kWh"}`), } Expect(resourceRepo.Create(context.TODO(), rf)).To(Succeed()) req, _ := http.NewRequest("DELETE", "/resources/rf-1", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) Expect(w.Code).To(Equal(http.StatusNoContent)) // Verify it's gone _, err := resourceRepo.GetByID(context.Background(), "rf-1") Expect(err).To(HaveOccurred()) }) }) })