diff --git a/pricing/pricing_mms43.go b/pricing/pricing_mms43.go index fd9e67b..7222027 100644 --- a/pricing/pricing_mms43.go +++ b/pricing/pricing_mms43.go @@ -7,14 +7,30 @@ func NewMMS43PricingService() (*MMS43PricingService, error) { } func (s *MMS43PricingService) Prices(params PricingParams) (map[string]Price, error) { + // Calculate passenger price + var passengerAmount float64 + + // First 2 trips are free (1 return trip or 2 outward trips) + freeTripsRemaining := 2 - params.Beneficiary.History + if freeTripsRemaining > 0 { + // Trip is free + passengerAmount = 0.0 + } else { + // Price is 0.15€/km for passenger distance (convert meters to km) + passengerAmount = 0.15 * (float64(params.SharedMobility.PassengerDistance) / 1000.0) + } + + // Driver indemnification is always 0.30€/km for driver distance (convert meters to km) + driverAmount := 0.30 * (float64(params.SharedMobility.DriverDistance) / 1000.0) + return map[string]Price{ "passenger": { - Amount: 0.32 * float64(params.SharedMobility.PassengerDistance), - Currency: "EUR/2", + Amount: passengerAmount, + Currency: "EUR", }, "driver": { - Amount: 0.32 * float64(params.SharedMobility.DriverDistance), - Currency: "EUR/2", + Amount: driverAmount, + Currency: "EUR", }, }, nil } diff --git a/pricing/pricing_mms43_test.go b/pricing/pricing_mms43_test.go new file mode 100644 index 0000000..b04d68e --- /dev/null +++ b/pricing/pricing_mms43_test.go @@ -0,0 +1,221 @@ +package pricing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMMS43Prices(t *testing.T) { + testCases := []struct { + name string + params PricingParams + expectedPrices map[string]Price + expectedError bool + }{ + { + name: "First trip - should be free for passenger", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 0, // First trip + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 10000, // 10km in meters + DriverDistance: 15000, // 15km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 0.0, Currency: "EUR"}, // Free for first trip + "driver": {Amount: 4.5, Currency: "EUR"}, // 15km * 0.30€/km + }, + expectedError: false, + }, + { + name: "Second trip - should be free for passenger", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 1, // Second trip + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 20000, // 20km in meters + DriverDistance: 25000, // 25km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 0.0, Currency: "EUR"}, // Free for second trip + "driver": {Amount: 7.5, Currency: "EUR"}, // 25km * 0.30€/km + }, + expectedError: false, + }, + { + name: "Third trip - passenger should pay", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 2, // Third trip + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 10000, // 10km in meters + DriverDistance: 10000, // 10km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 1.5, Currency: "EUR"}, // 10km * 0.15€/km + "driver": {Amount: 3.0, Currency: "EUR"}, // 10km * 0.30€/km + }, + expectedError: false, + }, + { + name: "Multiple trips - passenger pays normal rate", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 5, // Multiple trips + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 30000, // 30km in meters + DriverDistance: 35000, // 35km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 4.5, Currency: "EUR"}, // 30km * 0.15€/km + "driver": {Amount: 10.5, Currency: "EUR"}, // 35km * 0.30€/km + }, + expectedError: false, + }, + { + name: "Zero distance - should return zero amounts", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 3, + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 0, + DriverDistance: 0, + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 0.0, Currency: "EUR"}, + "driver": {Amount: 0.0, Currency: "EUR"}, + }, + expectedError: false, + }, + { + name: "Large distances - check calculation accuracy", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 10, + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 100000, // 100km in meters + DriverDistance: 120000, // 120km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 15.0, Currency: "EUR"}, // 100km * 0.15€/km + "driver": {Amount: 36.0, Currency: "EUR"}, // 120km * 0.30€/km + }, + expectedError: false, + }, + { + name: "Edge case - exactly 2 trips history (3rd trip)", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 2, // Exactly at the limit + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 4000, // 4km in meters + DriverDistance: 5000, // 5km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 0.6, Currency: "EUR"}, // 4km * 0.15€/km (first paid trip) + "driver": {Amount: 1.5, Currency: "EUR"}, // 5km * 0.30€/km + }, + expectedError: false, + }, + { + name: "Small distances with fractions", + params: PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 3, + Priority: false, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 2000, // 2km in meters + DriverDistance: 3000, // 3km in meters + }, + }, + expectedPrices: map[string]Price{ + "passenger": {Amount: 0.3, Currency: "EUR"}, // 2km * 0.15€/km + "driver": {Amount: 0.9, Currency: "EUR"}, // 3km * 0.30€/km + }, + expectedError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + pricingService, err := NewMMS43PricingService() + assert.NoError(t, err) + + prices, err := pricingService.Prices(tc.params) + if tc.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + // Use InDelta for floating-point comparisons to handle precision issues + assert.InDelta(t, tc.expectedPrices["passenger"].Amount, prices["passenger"].Amount, 0.001) + assert.Equal(t, tc.expectedPrices["passenger"].Currency, prices["passenger"].Currency) + assert.InDelta(t, tc.expectedPrices["driver"].Amount, prices["driver"].Amount, 0.001) + assert.Equal(t, tc.expectedPrices["driver"].Currency, prices["driver"].Currency) + } + }) + } +} + +func TestMMS43PricingEdgeCases(t *testing.T) { + pricingService, err := NewMMS43PricingService() + assert.NoError(t, err) + + t.Run("Negative history should still work", func(t *testing.T) { + params := PricingParams{ + Beneficiary: BeneficiaryParams{ + History: -1, // Edge case: negative history + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 10000, + DriverDistance: 10000, + }, + } + + prices, err := pricingService.Prices(params) + assert.NoError(t, err) + // Should still be free (2 - (-1) = 3 > 0) + assert.InDelta(t, 0.0, prices["passenger"].Amount, 0.001) + assert.InDelta(t, 3.0, prices["driver"].Amount, 0.001) + }) + + t.Run("Very high history", func(t *testing.T) { + params := PricingParams{ + Beneficiary: BeneficiaryParams{ + History: 100, + }, + SharedMobility: SharedMobilityParams{ + PassengerDistance: 10000, + DriverDistance: 10000, + }, + } + + prices, err := pricingService.Prices(params) + assert.NoError(t, err) + // Should charge normal rate + assert.InDelta(t, 1.5, prices["passenger"].Amount, 0.001) + assert.InDelta(t, 3.0, prices["driver"].Amount, 0.001) + }) +} \ No newline at end of file