package service_test import ( "context" "testing" "bugulma/backend/internal/domain" "bugulma/backend/internal/geospatial" "bugulma/backend/internal/repository" "bugulma/backend/internal/service" "bugulma/backend/internal/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "gorm.io/datatypes" "gorm.io/gorm" ) type SpatialResourceMatcherTestSuite struct { suite.Suite db *gorm.DB siteRepo domain.SiteRepository flowRepo domain.ResourceFlowRepository geoSvc *service.GeospatialService transportSvc *service.TransportationService geoCalc geospatial.Calculator matcher *service.SpatialResourceMatcher } func (suite *SpatialResourceMatcherTestSuite) SetupTest() { suite.db = testutils.SetupTestDB(suite.T()) suite.siteRepo = repository.NewSiteRepository(suite.db) suite.flowRepo = repository.NewResourceFlowRepository(suite.db) suite.geoSvc = service.NewGeospatialService(suite.db, nil) // We'll test without geo features for basic functionality suite.geoCalc = geospatial.NewCalculatorWithDefaults() suite.transportSvc = service.NewTransportationService(suite.geoCalc) suite.matcher = service.NewSpatialResourceMatcher( nil, // geoRepo - not needed for basic matching tests suite.siteRepo, suite.flowRepo, suite.geoSvc, suite.transportSvc, suite.geoCalc, ) // Create test organization org := &domain.Organization{ID: "org-test", Name: "Test Organization"} err := repository.NewOrganizationRepository(suite.db).Create(context.Background(), org) suite.Require().NoError(err) } func TestSpatialResourceMatcher(t *testing.T) { suite.Run(t, new(SpatialResourceMatcherTestSuite)) } func (suite *SpatialResourceMatcherTestSuite) TestNewSpatialResourceMatcher() { assert.NotNil(suite.T(), suite.matcher) } func (suite *SpatialResourceMatcherTestSuite) TestSiteProvidesResource_PlatformDefault() { // Create test site site := &domain.Site{ ID: "site-test", Name: "Test Site", Latitude: 52.5200, Longitude: 13.4050, SiteType: domain.SiteTypeIndustrial, OwnerOrganizationID: "org-test", } err := suite.siteRepo.Create(context.Background(), site) suite.Require().NoError(err) // Test that site creation worked assert.NotNil(suite.T(), site) } func (suite *SpatialResourceMatcherTestSuite) setupTestSitesAndFlows() []*domain.Site { // Create test sites sites := []*domain.Site{ { ID: "site-provider-1", Name: "Energy Provider Site", Latitude: 52.5200, Longitude: 13.4050, SiteType: domain.SiteTypeIndustrial, OwnerOrganizationID: "org-test", }, { ID: "site-provider-2", Name: "Waste Provider Site", Latitude: 52.5300, Longitude: 13.4150, SiteType: domain.SiteTypeIndustrial, OwnerOrganizationID: "org-test", }, { ID: "site-consumer", Name: "Manufacturing Site", Latitude: 52.5400, Longitude: 13.4250, SiteType: domain.SiteTypeIndustrial, OwnerOrganizationID: "org-test", }, } for _, site := range sites { err := suite.siteRepo.Create(context.Background(), site) suite.Require().NoError(err) } // Create test resource flows flows := []*domain.ResourceFlow{ { ID: "flow-energy-output", OrganizationID: "org-test", SiteID: "site-provider-1", Direction: domain.DirectionOutput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 1000, "unit": "kWh"}`), EconomicData: datatypes.JSON(`{"cost_out": 0.15}`), }, { ID: "flow-waste-output", OrganizationID: "org-test", SiteID: "site-provider-2", Direction: domain.DirectionOutput, Type: domain.TypeBiowaste, Quantity: datatypes.JSON(`{"amount": 500, "unit": "kg"}`), EconomicData: datatypes.JSON(`{"cost_out": 0.05}`), }, { ID: "flow-energy-input", OrganizationID: "org-test", SiteID: "site-consumer", Direction: domain.DirectionInput, Type: domain.TypeHeat, Quantity: datatypes.JSON(`{"amount": 800, "unit": "kWh"}`), EconomicData: datatypes.JSON(`{"cost_in": 0.20}`), }, } for _, flow := range flows { err := suite.flowRepo.Create(context.Background(), flow) suite.Require().NoError(err) } return sites } func (suite *SpatialResourceMatcherTestSuite) TestFindNearbyResourceProviders_Energy() { suite.setupTestSitesAndFlows() results, err := suite.matcher.FindNearbyResourceProviders( context.Background(), domain.TypeHeat, 52.5400, 13.4250, // Consumer location 10.0, // 10km radius domain.TransportModeTruck, ) assert.NoError(suite.T(), err) assert.Len(suite.T(), results, 1) // Should find one energy provider result := results[0] assert.Equal(suite.T(), "flow-energy-output", result.ResourceFlow.ID) assert.Equal(suite.T(), "site-provider-1", result.ProviderSite.ID) assert.NotNil(suite.T(), result.SpatialMetrics) assert.Greater(suite.T(), result.SpatialMetrics.StraightLineDistance, 0.0) assert.Greater(suite.T(), result.MatchScore, 0.0) } func (suite *SpatialResourceMatcherTestSuite) TestFindNearbyResourceProviders_Waste() { suite.setupTestSitesAndFlows() results, err := suite.matcher.FindNearbyResourceProviders( context.Background(), domain.TypeBiowaste, 52.5400, 13.4250, 10.0, domain.TransportModeTruck, ) assert.NoError(suite.T(), err) assert.Len(suite.T(), results, 1) result := results[0] assert.Equal(suite.T(), "flow-waste-output", result.ResourceFlow.ID) assert.Equal(suite.T(), "site-provider-2", result.ProviderSite.ID) } func (suite *SpatialResourceMatcherTestSuite) TestFindNearbyResourceProviders_NoMatches() { suite.setupTestSitesAndFlows() // Search for chemical resources (none exist) results, err := suite.matcher.FindNearbyResourceProviders( context.Background(), domain.TypeMaterials, 52.5400, 13.4250, 10.0, domain.TransportModeTruck, ) assert.NoError(suite.T(), err) assert.Len(suite.T(), results, 0) } func (suite *SpatialResourceMatcherTestSuite) TestFindNearbyResourceProviders_OutOfRadius() { suite.setupTestSitesAndFlows() // Search with very small radius results, err := suite.matcher.FindNearbyResourceProviders( context.Background(), domain.TypeHeat, 52.5400, 13.4250, 0.1, // Very small radius domain.TransportModeTruck, ) assert.NoError(suite.T(), err) assert.Len(suite.T(), results, 0) // No providers within 0.1km } func (suite *SpatialResourceMatcherTestSuite) TestSpatialMatchResult_JSONSerialization() { flow := &domain.ResourceFlow{ ID: "test-flow", Type: domain.TypeHeat, } providerSite := &domain.Site{ ID: "test-site", Name: "Test Provider", Latitude: 52.5200, Longitude: 13.4050, } metrics := &service.SpatialMetrics{ StraightLineDistance: 5.5, TransportCost: 42.5, EnvironmentalScore: 7.8, } result := &service.SpatialMatchResult{ ResourceFlow: flow, ProviderSite: providerSite, SpatialMetrics: metrics, MatchScore: 8.2, } // This would test JSON serialization if we had JSON tags // For now, just verify the struct is properly constructed assert.NotNil(suite.T(), result.ResourceFlow) assert.NotNil(suite.T(), result.ProviderSite) assert.NotNil(suite.T(), result.SpatialMetrics) assert.Equal(suite.T(), 8.2, result.MatchScore) }