335 lines
10 KiB
Go
335 lines
10 KiB
Go
package handler
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.coopgo.io/coopgo-platform/routing-service"
|
|
"git.coopgo.io/coopgo-platform/solidarity-transport/storage"
|
|
"git.coopgo.io/coopgo-platform/solidarity-transport/types"
|
|
"github.com/google/uuid"
|
|
"github.com/paulmach/orb"
|
|
"github.com/paulmach/orb/geojson"
|
|
"github.com/spf13/viper"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type MockRoutingService struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockRoutingService) Route(points []orb.Point) (*routing.Route, error) {
|
|
args := m.Called(points)
|
|
return args.Get(0).(*routing.Route), args.Error(1)
|
|
}
|
|
|
|
func createTestConfig() *viper.Viper {
|
|
cfg := viper.New()
|
|
cfg.Set("storage.db.type", "mock")
|
|
cfg.Set("parameters.limits.distance.min", 1000)
|
|
cfg.Set("parameters.limits.distance.max", 50000)
|
|
return cfg
|
|
}
|
|
|
|
func createTestGeoJSONFeature(lat, lng float64, label string) *geojson.Feature {
|
|
feature := geojson.NewFeature(orb.Point{lng, lat})
|
|
feature.Properties = make(map[string]interface{})
|
|
feature.Properties["label"] = label
|
|
return feature
|
|
}
|
|
|
|
func createTestRoute() *routing.Route {
|
|
return createTestRouteWithDistance(6000) // Default route with 6km passenger distance
|
|
}
|
|
|
|
func createTestRouteNoReturn(passengerDistance float64) *routing.Route {
|
|
// No-return: 3 legs (driver→pickup, pickup→dropoff, dropoff→driver)
|
|
return &routing.Route{
|
|
Summary: routing.Summary{
|
|
Distance: 4000 + passengerDistance,
|
|
Duration: 15 * time.Minute,
|
|
Polyline: "test_polyline",
|
|
},
|
|
Legs: []routing.RouteLeg{
|
|
{Summary: routing.Summary{Distance: 2000, Duration: 5 * time.Minute}}, // driver to pickup
|
|
{Summary: routing.Summary{Distance: passengerDistance, Duration: 8 * time.Minute}}, // pickup to dropoff
|
|
{Summary: routing.Summary{Distance: 2000, Duration: 5 * time.Minute}}, // dropoff to driver destination
|
|
},
|
|
}
|
|
}
|
|
|
|
func createTestRouteReturn(passengerDistance float64) *routing.Route {
|
|
// Return: 4+ legs (driver→pickup, pickup→dropoff, dropoff→pickup, pickup→driver)
|
|
// For return journeys: passenger distance = legs[1] + legs[2]
|
|
singleLegDistance := passengerDistance / 2
|
|
|
|
return &routing.Route{
|
|
Summary: routing.Summary{
|
|
Distance: 4000 + passengerDistance,
|
|
Duration: 18 * time.Minute,
|
|
Polyline: "test_polyline",
|
|
},
|
|
Legs: []routing.RouteLeg{
|
|
{Summary: routing.Summary{Distance: 2000, Duration: 5 * time.Minute}}, // driver to pickup
|
|
{Summary: routing.Summary{Distance: singleLegDistance, Duration: 8 * time.Minute}}, // pickup to dropoff
|
|
{Summary: routing.Summary{Distance: singleLegDistance, Duration: 2 * time.Minute}}, // dropoff to pickup (return)
|
|
{Summary: routing.Summary{Distance: 2000, Duration: 3 * time.Minute}}, // pickup to driver destination
|
|
},
|
|
}
|
|
}
|
|
|
|
func createTestRouteWithDistance(passengerDistance float64) *routing.Route {
|
|
// Default to return journey for backward compatibility
|
|
return createTestRouteReturn(passengerDistance)
|
|
}
|
|
|
|
func createTestAvailability(driverID string, day int, startTime, endTime string) *types.DriverRegularAvailability {
|
|
address := createTestGeoJSONFeature(45.7640, 4.8357, "Driver Address")
|
|
return &types.DriverRegularAvailability{
|
|
ID: uuid.New().String(),
|
|
DriverId: driverID,
|
|
Day: day,
|
|
StartTime: startTime,
|
|
EndTime: endTime,
|
|
Address: address,
|
|
}
|
|
}
|
|
|
|
func TestHandler_GetDriverJourneys(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
departureDate time.Time
|
|
noreturn bool
|
|
availabilities []*types.DriverRegularAvailability
|
|
routingResponse *routing.Route
|
|
expectedJourneys int
|
|
expectedDriverIDs []string
|
|
description string
|
|
}{
|
|
{
|
|
name: "journey within distance limits",
|
|
departureDate: time.Date(2024, 1, 15, 8, 0, 0, 0, time.UTC),
|
|
noreturn: false,
|
|
availabilities: []*types.DriverRegularAvailability{
|
|
createTestAvailability("driver-1", 1, "07:00", "09:00"),
|
|
},
|
|
routingResponse: createTestRouteWithDistance(6000), // 6km - within limits (1km to 50km)
|
|
expectedJourneys: 1,
|
|
expectedDriverIDs: []string{"driver-1"},
|
|
description: "6km journey should be within distance limits",
|
|
},
|
|
{
|
|
name: "journey too short - below minimum distance",
|
|
departureDate: time.Date(2024, 1, 15, 8, 0, 0, 0, time.UTC),
|
|
noreturn: false,
|
|
availabilities: []*types.DriverRegularAvailability{
|
|
createTestAvailability("driver-1", 1, "07:00", "09:00"),
|
|
},
|
|
routingResponse: createTestRouteWithDistance(500), // 500m - below 1km minimum
|
|
expectedJourneys: 0,
|
|
expectedDriverIDs: []string{},
|
|
description: "500m journey should be rejected (below minimum distance)",
|
|
},
|
|
{
|
|
name: "journey too long - above maximum distance",
|
|
departureDate: time.Date(2024, 1, 15, 8, 0, 0, 0, time.UTC),
|
|
noreturn: false,
|
|
availabilities: []*types.DriverRegularAvailability{
|
|
createTestAvailability("driver-1", 1, "07:00", "09:00"),
|
|
},
|
|
routingResponse: createTestRouteWithDistance(60000), // 60km - above 50km maximum
|
|
expectedJourneys: 0,
|
|
expectedDriverIDs: []string{},
|
|
description: "60km journey should be rejected (above maximum distance)",
|
|
},
|
|
{
|
|
name: "journey at minimum distance boundary",
|
|
departureDate: time.Date(2024, 1, 15, 8, 0, 0, 0, time.UTC),
|
|
noreturn: false,
|
|
availabilities: []*types.DriverRegularAvailability{
|
|
createTestAvailability("driver-1", 1, "07:00", "09:00"),
|
|
},
|
|
routingResponse: createTestRouteWithDistance(1000), // Exactly 1km minimum
|
|
expectedJourneys: 1,
|
|
expectedDriverIDs: []string{"driver-1"},
|
|
description: "1km journey should be accepted (at minimum boundary)",
|
|
},
|
|
{
|
|
name: "journey at maximum distance boundary",
|
|
departureDate: time.Date(2024, 1, 15, 8, 0, 0, 0, time.UTC),
|
|
noreturn: false,
|
|
availabilities: []*types.DriverRegularAvailability{
|
|
createTestAvailability("driver-1", 1, "07:00", "09:00"),
|
|
},
|
|
routingResponse: createTestRouteWithDistance(50000), // Exactly 50km maximum
|
|
expectedJourneys: 1,
|
|
expectedDriverIDs: []string{"driver-1"},
|
|
description: "50km journey should be accepted (at maximum boundary)",
|
|
},
|
|
{
|
|
name: "wrong day no availabilities",
|
|
departureDate: time.Date(2024, 1, 16, 9, 0, 0, 0, time.UTC),
|
|
noreturn: false,
|
|
availabilities: []*types.DriverRegularAvailability{
|
|
createTestAvailability("driver-1", 1, "08:00", "10:00"),
|
|
},
|
|
expectedJourneys: 0,
|
|
expectedDriverIDs: []string{},
|
|
description: "Tuesday departure should not match Monday availability",
|
|
},
|
|
}
|
|
|
|
departure := createTestGeoJSONFeature(45.7578, 4.8320, "Departure")
|
|
arrival := createTestGeoJSONFeature(45.7485, 4.8467, "Arrival")
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := createTestConfig()
|
|
mockStorage := storage.NewMockStorage(cfg)
|
|
mockRouting := &MockRoutingService{}
|
|
|
|
for _, availability := range tt.availabilities {
|
|
err := mockStorage.CreateDriverRegularAvailability(*availability)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
routeResponse := tt.routingResponse
|
|
if routeResponse == nil {
|
|
routeResponse = createTestRoute() // Default route
|
|
}
|
|
mockRouting.On("Route", mock.AnythingOfType("[]orb.Point")).Return(routeResponse, nil)
|
|
|
|
handler := &Handler{
|
|
Config: cfg,
|
|
Storage: mockStorage,
|
|
Routing: mockRouting,
|
|
}
|
|
|
|
journeys, err := handler.GetDriverJourneys(departure, arrival, tt.departureDate, tt.noreturn)
|
|
|
|
assert.NoError(t, err, tt.description)
|
|
assert.Len(t, journeys, tt.expectedJourneys, tt.description)
|
|
|
|
if tt.expectedJourneys > 0 {
|
|
actualDriverIDs := make([]string, len(journeys))
|
|
for i, journey := range journeys {
|
|
assert.NotEmpty(t, journey.Id)
|
|
actualDriverIDs[i] = journey.DriverId
|
|
}
|
|
assert.ElementsMatch(t, tt.expectedDriverIDs, actualDriverIDs, tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandler_GetDriverJourney(t *testing.T) {
|
|
cfg := createTestConfig()
|
|
mockStorage := storage.NewMockStorage(cfg)
|
|
|
|
testJourney := &types.DriverJourney{
|
|
Id: uuid.New().String(),
|
|
DriverId: "driver-1",
|
|
}
|
|
err := mockStorage.PushDriverJourneys([]*types.DriverJourney{testJourney})
|
|
require.NoError(t, err)
|
|
|
|
handler := &Handler{
|
|
Config: cfg,
|
|
Storage: mockStorage,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
driverID string
|
|
journeyID string
|
|
expectedError bool
|
|
errorMessage string
|
|
}{
|
|
{
|
|
name: "successful retrieval",
|
|
driverID: "driver-1",
|
|
journeyID: testJourney.Id,
|
|
expectedError: false,
|
|
},
|
|
{
|
|
name: "driver mismatch",
|
|
driverID: "driver-2",
|
|
journeyID: testJourney.Id,
|
|
expectedError: true,
|
|
errorMessage: "not allowed, driver id mismatch",
|
|
},
|
|
{
|
|
name: "journey not found",
|
|
driverID: "driver-1",
|
|
journeyID: "non-existent",
|
|
expectedError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
journey, err := handler.GetDriverJourney(tt.driverID, tt.journeyID)
|
|
|
|
if tt.expectedError {
|
|
assert.Error(t, err)
|
|
if tt.errorMessage != "" {
|
|
assert.Contains(t, err.Error(), tt.errorMessage)
|
|
}
|
|
assert.Nil(t, journey)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, journey)
|
|
assert.Equal(t, tt.journeyID, journey.Id)
|
|
assert.Equal(t, tt.driverID, journey.DriverId)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandler_ToggleDriverJourneyNoreturn(t *testing.T) {
|
|
cfg := createTestConfig()
|
|
mockStorage := storage.NewMockStorage(cfg)
|
|
mockRouting := &MockRoutingService{}
|
|
|
|
departure := createTestGeoJSONFeature(45.7578, 4.8320, "Departure")
|
|
arrival := createTestGeoJSONFeature(45.7485, 4.8467, "Arrival")
|
|
driverAddress := createTestGeoJSONFeature(45.7640, 4.8357, "Driver Address")
|
|
|
|
testJourney := &types.DriverJourney{
|
|
Id: uuid.New().String(),
|
|
DriverId: "driver-1",
|
|
PassengerPickup: departure,
|
|
PassengerDrop: arrival,
|
|
DriverDeparture: driverAddress,
|
|
Noreturn: false,
|
|
}
|
|
|
|
err := mockStorage.PushDriverJourneys([]*types.DriverJourney{testJourney})
|
|
require.NoError(t, err)
|
|
|
|
mockRouting.On("Route", mock.AnythingOfType("[]orb.Point")).Return(createTestRoute(), nil)
|
|
|
|
handler := &Handler{
|
|
Config: cfg,
|
|
Storage: mockStorage,
|
|
Routing: mockRouting,
|
|
}
|
|
|
|
err = handler.ToggleDriverJourneyNoreturn(testJourney.Id)
|
|
assert.NoError(t, err)
|
|
|
|
updatedJourney, err := mockStorage.GetDriverJourney(testJourney.Id)
|
|
assert.NoError(t, err)
|
|
assert.True(t, updatedJourney.Noreturn)
|
|
|
|
err = handler.ToggleDriverJourneyNoreturn(testJourney.Id)
|
|
assert.NoError(t, err)
|
|
|
|
updatedJourney, err = mockStorage.GetDriverJourney(testJourney.Id)
|
|
assert.NoError(t, err)
|
|
assert.False(t, updatedJourney.Noreturn)
|
|
|
|
mockRouting.AssertExpectations(t)
|
|
} |