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 // Haversine distance for Berlin->Hamburg can be ~255km; use a lower bound assert.Greater(suite.T(), cost.StraightDistanceKm, 250.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 } 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, 0.0) // Some modes (like pipeline) may have very low utilization at small volumes. } 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) // Pipeline best for environment } 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(), 200.0, profile.MaxCapacity) assert.Equal(suite.T(), 0.7, profile.EnvironmentalFactor) // Rail better for environment } 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 // Some modes (like pipeline) may have very low utilization at small volumes. // Ensure there is at least a non-zero utilization percentage. assert.Greater(suite.T(), option.CapacityUtilization, 0.0) 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) // Environmental score is computed as 10.0 / factor. For pipeline with 0.5, // the expected score is 20.0. assert.Equal(suite.T(), 20.0, pipelineOption.EnvironmentalScore) }