turash/bugulma/backend/internal/service/transportation_service_test.go
Damir Mukimov 0df4812c82
feat: Complete geographical features implementation with full test coverage
- Add comprehensive geographical data models (GeographicalFeature, TransportMode, TransportProfile, TransportOption)
- Implement geographical feature repository with PostGIS support and spatial queries
- Create transportation service for cost calculation and route optimization
- Build spatial resource matcher for geographical resource matching
- Develop environmental impact service for site environmental scoring
- Implement facility location optimizer with multi-criteria analysis
- Add geographical data migration service for SQLite to PostgreSQL migration
- Create database migrations for geographical features and site footprints
- Update geospatial service integration and server initialization
- Add CLI command for geographical data synchronization
- Implement complete test coverage for all geographical components (28 test cases)
- Update test infrastructure for geographical table creation and PostGIS handling

This implements advanced geospatial capabilities including transportation cost modeling, environmental impact assessment, and facility location optimization for the Turash platform.
2025-11-25 06:42:18 +01:00

201 lines
8.5 KiB
Go

package service_test
import (
"testing"
"bugulma/backend/internal/domain"
"bugulma/backend/internal/geospatial"
"bugulma/backend/internal/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type TransportationServiceTestSuite struct {
suite.Suite
geoCalc geospatial.Calculator
svc *service.TransportationService
}
func (suite *TransportationServiceTestSuite) SetupTest() {
suite.geoCalc = geospatial.NewCalculatorWithDefaults()
suite.svc = service.NewTransportationService(suite.geoCalc)
}
func TestTransportationService(t *testing.T) {
suite.Run(t, new(TransportationServiceTestSuite))
}
func (suite *TransportationServiceTestSuite) TestNewTransportationService() {
assert.NotNil(suite.T(), suite.svc)
}
func (suite *TransportationServiceTestSuite) TestCalculateTransportCost_Truck() {
cost, err := suite.svc.CalculateTransportCost(52.5200, 13.4050, 53.5511, 9.9937, domain.TransportModeTruck, 15.0)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), cost)
// Verify cost calculation (Berlin to Hamburg, ~290km, truck cost ~€0.12/km)
assert.Greater(suite.T(), cost.CostEur, 30.0) // Should be around €34.80
assert.Greater(suite.T(), cost.StraightDistanceKm, 280.0)
assert.Greater(suite.T(), cost.RoadDistanceKm, cost.StraightDistanceKm) // Road distance > straight distance
assert.Greater(suite.T(), cost.TimeHours, 4.0) // Should take several hours
assert.Equal(suite.T(), domain.TransportModeTruck, cost.TransportMode)
assert.Equal(suite.T(), 1.0, cost.EnvironmentalFactor) // Truck baseline
assert.Greater(suite.T(), cost.CapacityUtilization, 50.0) // 15/25 = 60% utilization
}
func (suite *TransportationServiceTestSuite) TestCalculateTransportCost_Rail() {
cost, err := suite.svc.CalculateTransportCost(52.5200, 13.4050, 53.5511, 9.9937, domain.TransportModeRail, 50.0)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), cost)
// Rail should be cheaper than truck
assert.Greater(suite.T(), cost.CostEur, 20.0) // Should be around €23.20 (rail €0.08/km)
assert.Equal(suite.T(), domain.TransportModeRail, cost.TransportMode)
assert.Equal(suite.T(), 0.7, cost.EnvironmentalFactor) // Rail better for environment
assert.Greater(suite.T(), cost.CapacityUtilization, 10.0) // 50/100 = 50% utilization
}
func (suite *TransportationServiceTestSuite) TestCalculateTransportCost_Pipeline() {
cost, err := suite.svc.CalculateTransportCost(52.5200, 13.4050, 53.5511, 9.9937, domain.TransportModePipe, 500.0)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), cost)
// Pipeline should be cheapest
assert.Greater(suite.T(), cost.CostEur, 5.0) // Should be around €5.80 (pipeline €0.02/km)
assert.Equal(suite.T(), domain.TransportModePipe, cost.TransportMode)
assert.Equal(suite.T(), 0.5, cost.EnvironmentalFactor) // Pipeline best for environment
assert.Greater(suite.T(), cost.CapacityUtilization, 5.0) // 500/1000 = 50% utilization
}
func (suite *TransportationServiceTestSuite) TestCalculateTransportCost_VolumeExceedsCapacity() {
// Try to transport 150 tons with truck (max 25 tons)
_, err := suite.svc.CalculateTransportCost(52.5200, 13.4050, 53.5511, 9.9937, domain.TransportModeTruck, 150.0)
assert.Error(suite.T(), err)
assert.Contains(suite.T(), err.Error(), "transport volume exceeds capacity")
}
func (suite *TransportationServiceTestSuite) TestFindOptimalTransportRoutes() {
options, err := suite.svc.FindOptimalTransportRoutes(52.5200, 13.4050, 53.5511, 9.9937, 20.0)
assert.NoError(suite.T(), err)
assert.Len(suite.T(), options, 3) // Should return all 3 transport modes
// Verify all transport modes are present
modeCount := make(map[domain.TransportMode]bool)
for _, option := range options {
modeCount[option.TransportMode] = true
}
assert.True(suite.T(), modeCount[domain.TransportModeTruck])
assert.True(suite.T(), modeCount[domain.TransportModeRail])
assert.True(suite.T(), modeCount[domain.TransportModePipe])
// Verify they're sorted by overall score (highest first)
for i := 0; i < len(options)-1; i++ {
assert.GreaterOrEqual(suite.T(), options[i].OverallScore, options[i+1].OverallScore)
}
// Pipeline should generally have the highest score due to cost and environment
// (though this depends on the exact scoring algorithm)
found := false
for _, option := range options {
if option.TransportMode == domain.TransportModePipe {
found = true
break
}
}
assert.True(suite.T(), found)
}
func (suite *TransportationServiceTestSuite) TestFindOptimalTransportRoutes_LargeVolume() {
// Test with large volume that excludes some transport modes
options, err := suite.svc.FindOptimalTransportRoutes(52.5200, 13.4050, 53.5511, 9.9937, 150.0)
assert.NoError(suite.T(), err)
// Truck should be excluded (25 ton capacity), others should remain
for _, option := range options {
assert.NotEqual(suite.T(), domain.TransportModeTruck, option.TransportMode)
}
assert.Len(suite.T(), options, 2) // Rail and Pipeline only
}
func (suite *TransportationServiceTestSuite) TestGetTransportProfile_Truck() {
profile, err := suite.svc.GetTransportProfile(domain.TransportModeTruck)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), 0.12, profile.CostPerKm)
assert.Equal(suite.T(), 60.0, profile.SpeedKmH)
assert.Equal(suite.T(), 25.0, profile.MaxCapacity)
assert.Equal(suite.T(), 1.0, profile.EnvironmentalFactor)
}
func (suite *TransportationServiceTestSuite) TestGetTransportProfile_Rail() {
profile, err := suite.svc.GetTransportProfile(domain.TransportModeRail)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), 0.08, profile.CostPerKm)
assert.Equal(suite.T(), 40.0, profile.SpeedKmH)
assert.Equal(suite.T(), 100.0, profile.MaxCapacity)
assert.Equal(suite.T(), 0.7, profile.EnvironmentalFactor)
}
func (suite *TransportationServiceTestSuite) TestGetTransportProfile_Pipeline() {
profile, err := suite.svc.GetTransportProfile(domain.TransportModePipe)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), 0.05, profile.CostPerKm)
assert.Equal(suite.T(), 100.0, profile.SpeedKmH)
assert.Equal(suite.T(), 1000.0, profile.MaxCapacity)
assert.Equal(suite.T(), 0.5, profile.EnvironmentalFactor)
}
func (suite *TransportationServiceTestSuite) TestGetTransportProfile_InvalidMode() {
_, err := suite.svc.GetTransportProfile(domain.TransportMode("invalid"))
assert.Error(suite.T(), err)
assert.Contains(suite.T(), err.Error(), "invalid transport mode")
}
func (suite *TransportationServiceTestSuite) TestTransportCost_SameLocation() {
// Test transport cost for same location (should still have some cost)
cost, err := suite.svc.CalculateTransportCost(52.5200, 13.4050, 52.5200, 13.4050, domain.TransportModeTruck, 10.0)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), cost)
// Distance should be very small
assert.Less(suite.T(), cost.StraightDistanceKm, 0.1)
assert.GreaterOrEqual(suite.T(), cost.CostEur, 0.0) // Should still have some cost
}
func (suite *TransportationServiceTestSuite) TestTransportOptions_CompleteData() {
options, err := suite.svc.FindOptimalTransportRoutes(50.1109, 8.6821, 52.5200, 13.4050, 10.0) // Frankfurt to Berlin
assert.NoError(suite.T(), err)
assert.NotEmpty(suite.T(), options)
// Verify all required fields are populated
for _, option := range options {
assert.NotEqual(suite.T(), domain.TransportMode(""), option.TransportMode)
assert.Greater(suite.T(), option.DistanceKm, 500.0) // Frankfurt to Berlin is ~550km
assert.Greater(suite.T(), option.CostEur, 10.0) // Should have meaningful cost
assert.Greater(suite.T(), option.TimeHours, 5.0) // Should take several hours
assert.GreaterOrEqual(suite.T(), option.EnvironmentalScore, 5.0) // Should have environmental score
assert.GreaterOrEqual(suite.T(), option.CapacityUtilization, 10.0) // Should have utilization percentage
assert.NotEqual(suite.T(), 0.0, option.OverallScore) // Should have overall score
}
}
func (suite *TransportationServiceTestSuite) TestEnvironmentalFactor_Impact() {
// Test that environmental factor affects scoring
options, err := suite.svc.FindOptimalTransportRoutes(52.5200, 13.4050, 53.5511, 9.9937, 10.0)
assert.NoError(suite.T(), err)
// Find pipeline option (should have highest environmental score)
var pipelineOption *domain.TransportOption
for _, option := range options {
if option.TransportMode == domain.TransportModePipe {
pipelineOption = option
break
}
}
assert.NotNil(suite.T(), pipelineOption)
assert.Equal(suite.T(), 10.0, pipelineOption.EnvironmentalScore) // Pipeline has factor 0.5, score = 10/0.5 = 10
}