flamingo.me/flamingo-commerce/v3@v3.11.0/product/domain/productBasics_test.go (about) 1 package domain 2 3 import ( 4 "fmt" 5 "math" 6 "math/big" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "flamingo.me/flamingo-commerce/v3/price/domain" 14 ) 15 16 func TestAttributeValue(t *testing.T) { 17 a := Attribute{RawValue: "testValue"} 18 19 assert.Equal(t, a.Value(), "testValue") 20 } 21 22 func TestAttributeIsEnabledValue(t *testing.T) { 23 a := Attribute{RawValue: "Yes"} 24 assert.True(t, a.IsEnabledValue()) 25 26 a.RawValue = "yes" 27 assert.True(t, a.IsEnabledValue()) 28 29 a.RawValue = "true" 30 assert.True(t, a.IsEnabledValue()) 31 32 a.RawValue = true 33 assert.True(t, a.IsEnabledValue()) 34 35 a.RawValue = "1" 36 assert.True(t, a.IsEnabledValue()) 37 38 a.RawValue = 1 39 assert.True(t, a.IsEnabledValue()) 40 41 a.RawValue = "anything" 42 assert.False(t, a.IsEnabledValue()) 43 } 44 45 func TestAttributeIsDisabledValue(t *testing.T) { 46 a := Attribute{RawValue: "No"} 47 assert.True(t, a.IsDisabledValue()) 48 49 a.RawValue = "no" 50 assert.True(t, a.IsDisabledValue()) 51 52 a.RawValue = "false" 53 assert.True(t, a.IsDisabledValue()) 54 55 a.RawValue = false 56 assert.True(t, a.IsDisabledValue()) 57 58 a.RawValue = "0" 59 assert.True(t, a.IsDisabledValue()) 60 61 a.RawValue = 0 62 assert.True(t, a.IsDisabledValue()) 63 64 a.RawValue = "anything" 65 assert.False(t, a.IsDisabledValue()) 66 } 67 68 func TestAttributeHasMultipleValues(t *testing.T) { 69 t.Run("[]interface{} raw value", func(t *testing.T) { 70 a := Attribute{RawValue: "some string"} 71 assert.False(t, a.HasMultipleValues()) 72 73 var rawValue []interface{} 74 for _, val := range []string{"some", "string"} { 75 rawValue = append(rawValue, val) 76 } 77 a.RawValue = rawValue 78 79 assert.True(t, a.HasMultipleValues()) 80 }) 81 82 t.Run("Translated multi value", func(t *testing.T) { 83 a := Attribute{RawValue: []Attribute{{Code: "foo"}}} 84 assert.True(t, a.HasMultipleValues()) 85 }) 86 } 87 88 func TestAttributeValues(t *testing.T) { 89 t.Run("string values", func(t *testing.T) { 90 a := Attribute{RawValue: "some string"} 91 result := a.Values() 92 assert.IsType(t, []string{}, result) 93 assert.Len(t, result, 0) 94 95 var rawValue []interface{} 96 for _, val := range []string{"some", " string "} { 97 rawValue = append(rawValue, val) 98 } 99 a.RawValue = rawValue 100 result = a.Values() 101 assert.IsType(t, []string{}, result) 102 assert.Len(t, result, 2) 103 assert.Equal(t, "some", result[0]) 104 assert.Equal(t, "string", result[1]) 105 }) 106 107 t.Run("translated values", func(t *testing.T) { 108 a := Attribute{RawValue: []Attribute{{Label: "translation-A", RawValue: "raw-A"}, {Label: "translation-B", RawValue: "raw-B"}}} 109 values := a.Values() 110 assert.Len(t, values, 2) 111 assert.Equal(t, "raw-A", values[0]) 112 assert.Equal(t, "raw-B", values[1]) 113 }) 114 } 115 116 func TestAttributeLabels(t *testing.T) { 117 t.Run("translated values", func(t *testing.T) { 118 a := Attribute{RawValue: []Attribute{{Label: "translation-A", RawValue: "raw-A"}, {Label: "translation-B", RawValue: "raw-B"}}} 119 labels := a.Labels() 120 assert.Len(t, labels, 2) 121 assert.Equal(t, "translation-A", labels[0]) 122 assert.Equal(t, "translation-B", labels[1]) 123 }) 124 125 t.Run("no translated values will fallback to raw values", func(t *testing.T) { 126 a := Attribute{} 127 var rawValue []interface{} 128 for _, val := range []string{"raw-1", "raw-2"} { 129 rawValue = append(rawValue, val) 130 } 131 a.RawValue = rawValue 132 133 labels := a.Labels() 134 assert.Len(t, labels, 2) 135 assert.Equal(t, "raw-1", labels[0]) 136 assert.Equal(t, "raw-2", labels[1]) 137 }) 138 } 139 140 func TestAttributeHasUnit(t *testing.T) { 141 a := Attribute{} 142 assert.False(t, a.HasUnitCode()) 143 144 a.UnitCode = "UNIT" 145 assert.True(t, a.HasUnitCode()) 146 } 147 148 func TestAttributeGetUnit(t *testing.T) { 149 a := Attribute{} 150 assert.IsType(t, Unit{}, a.GetUnit(), "Get an empty unit if not available") 151 152 a.UnitCode = "PCS" 153 assert.IsType(t, Unit{}, a.GetUnit(), "Get a unit if available") 154 assert.Equal(t, "PCS", a.GetUnit().Code, "Fetched unit contains the correct content") 155 } 156 157 func TestBasicProductHasAttribute(t *testing.T) { 158 b := BasicProductData{} 159 assert.False(t, b.HasAttribute("code")) 160 161 b.Attributes = map[string]Attribute{"code": {Code: "code"}} 162 assert.True(t, b.HasAttribute("code")) 163 assert.False(t, b.HasAttribute("Code")) 164 } 165 166 func TestBasicProductHasAllAttributes(t *testing.T) { 167 b := BasicProductData{} 168 assert.False(t, b.HasAllAttributes([]string{"code", "color"})) 169 170 b.Attributes = map[string]Attribute{"code": {Code: "code"}, "color": {Code: "color"}} 171 assert.True(t, b.HasAllAttributes([]string{"code", "color"})) 172 assert.False(t, b.HasAllAttributes([]string{"Code", "Color"})) 173 } 174 175 func TestBasicProductHasGetAttributesByKey(t *testing.T) { 176 b := BasicProductData{} 177 b.Attributes = map[string]Attribute{ 178 "foo": {Code: "foo"}, 179 "bar": {Code: "bar"}, 180 } 181 assert.Equal(t, []Attribute{ 182 {Code: "foo"}, 183 {Code: "bar"}, 184 }, b.Attributes.AttributesByKey([]string{"foo", "bar"})) 185 186 assert.Equal(t, []Attribute{ 187 {Code: "bar"}, 188 {Code: "foo"}, 189 }, b.Attributes.AttributesByKey([]string{"bar", "foo"})) 190 191 assert.Equal(t, []Attribute{ 192 {Code: "foo"}, 193 {Code: "bar"}, 194 }, b.Attributes.AttributesByKey([]string{"foo", "baz", "bar"})) 195 } 196 197 func TestBasicProductGetFinalPrice(t *testing.T) { 198 p := PriceInfo{ 199 IsDiscounted: false, 200 Discounted: domain.NewFromFloat(0.99, "EUR"), 201 Default: domain.NewFromFloat(1.99, "EUR"), 202 } 203 assert.Equal(t, 1.99, p.GetFinalPrice().FloatAmount()) 204 205 p.IsDiscounted = true 206 assert.Equal(t, 0.99, p.GetFinalPrice().FloatAmount()) 207 } 208 209 func TestBasicProductGetMedia(t *testing.T) { 210 var m []Media 211 p := BasicProductData{Media: m} 212 213 result := p.GetMedia("something") 214 assert.IsType(t, Media{}, result, "Media returned on unknown type") 215 assert.Empty(t, result.Usage, "empty media returned on unknown type") 216 217 result = p.GetListMedia() 218 assert.Empty(t, result.Usage, "empty list media returned if it does not exist") 219 220 p.Media = append(m, Media{Usage: MediaUsageList}) 221 result = p.GetMedia(MediaUsageList) 222 assert.Equal(t, "list", result.Usage, "media with correct usage returned if exists") 223 224 result = p.GetListMedia() 225 assert.Equal(t, "list", result.Usage, "list media returned if exists") 226 } 227 228 func TestBasicProductBadgesGetFirst(t *testing.T) { 229 t.Parallel() 230 var badges Badges 231 232 assert.Nil(t, badges.First(), "get nil if badges are nil - don't fail") 233 234 badges = Badges{} 235 assert.Nil(t, badges.First(), "get nil if badges are empty") 236 237 badges = Badges{ 238 { 239 Code: "first", 240 Label: "First", 241 }, 242 { 243 Code: "second", 244 Label: "Second", 245 }, 246 } 247 assert.Equal(t, &Badge{Code: "first", Label: "First"}, badges.First(), "get the first badge") 248 } 249 250 func TestIsSaleableNow(t *testing.T) { 251 s := Saleable{} 252 assert.False(t, s.IsSaleableNow(), "not saleable now if nothing is set") 253 254 s.IsSaleable = true 255 assert.True(t, s.IsSaleableNow(), "saleable now if just saleable") 256 257 future := time.Now().Add(time.Hour) 258 past := time.Now().Add(-time.Hour) 259 s.SaleableFrom = future 260 assert.False(t, s.IsSaleableNow(), "not saleable now if saleable from is in future") 261 s.SaleableFrom = past 262 assert.True(t, s.IsSaleableNow(), "saleable now if saleable from is in the past") 263 264 s.SaleableTo = past 265 assert.False(t, s.IsSaleableNow(), "not saleable now if saleable to is in the past") 266 s.SaleableTo = future 267 assert.True(t, s.IsSaleableNow(), "saleable now if saleable to is in the future") 268 } 269 270 func TestSaleable_GetLoyaltyChargeSplit(t *testing.T) { 271 t.Parallel() 272 273 t.Run("get correct charges", func(t *testing.T) { 274 t.Parallel() 275 276 p := Saleable{ 277 ActivePrice: PriceInfo{ 278 // 100EUR value 279 Default: domain.NewFromInt(100, 1, "EUR"), 280 }, 281 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 282 Type: "loyalty.miles", 283 MaxPointsToSpent: new(big.Float).SetInt64(50), 284 // 10 is the minimum to pay in miles (=20EUR value) 285 MinPointsToSpent: *new(big.Float).SetInt64(10), 286 // 50 miles == 100EUR meaning 1Mile = 2EUR 287 Default: domain.NewFromInt(50, 1, "Miles"), 288 }, 289 } 290 291 // Test default charges (the min price in points should be evaluated) 292 charges := p.GetLoyaltyChargeSplit(nil, nil, 1) 293 294 chargeLoyaltyMiles, found := charges.GetByType("loyalty.miles") 295 assert.True(t, found) 296 assert.Equal(t, domain.NewFromInt(10, 1, "Miles"), chargeLoyaltyMiles.Price, "only minimum points expected") 297 298 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 299 assert.True(t, found) 300 assert.Equal(t, domain.NewFromInt(80, 1, "EUR"), chargeMain.Price) 301 302 // Test when we pass 15 miles as wish 303 wished := NewWishedToPay().Add("loyalty.miles", domain.NewFromInt(15, 1, "Miles")) 304 charges = p.GetLoyaltyChargeSplit(nil, &wished, 1) 305 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 306 assert.True(t, found) 307 308 assert.Equal(t, domain.NewFromInt(70, 1, "EUR"), chargeMain.Price) 309 310 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 311 assert.True(t, found) 312 assert.Equal(t, domain.NewFromInt(15, 1, "Miles"), chargeLoyaltyMiles.Price, "the whished 15 points expected") 313 314 // Test when we pass 100 miles as wish 315 wished = NewWishedToPay().Add("loyalty.miles", domain.NewFromInt(100, 1, "Miles")) 316 charges = p.GetLoyaltyChargeSplit(nil, &wished, 1) 317 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 318 assert.True(t, found) 319 320 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "Main charge should be 0") 321 322 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 323 assert.True(t, found) 324 assert.Equal(t, domain.NewFromInt(50, 1, "Miles"), chargeLoyaltyMiles.Price, "50 points expected as max") 325 326 // Test when we pass 30 miles as desired payment and wish for qty 2 327 wished = NewWishedToPay().Add("loyalty.miles", domain.NewFromInt(30, 1, "Miles")) 328 doublePrice := p.ActivePrice.GetFinalPrice().Multiply(2) 329 charges = p.GetLoyaltyChargeSplit(&doublePrice, &wished, 1) 330 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 331 assert.True(t, found) 332 333 assert.Equal(t, domain.NewFromInt(140, 1, "EUR"), chargeMain.Price) 334 335 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 336 assert.True(t, found) 337 assert.Equal(t, domain.NewFromInt(30, 1, "Miles"), chargeLoyaltyMiles.Price, "the whished 30 points expected") 338 }) 339 340 t.Run("empty charges, when active loyalty is nil", func(t *testing.T) { 341 t.Parallel() 342 343 p := Saleable{ 344 ActivePrice: PriceInfo{ 345 // 100EUR value 346 Default: domain.NewFromInt(100, 1, "EUR"), 347 }, 348 ActiveLoyaltyPrice: nil, 349 } 350 351 // Test default charges (the min price in points should be evaluated) 352 charges := p.GetLoyaltyChargeSplit(nil, nil, 1) 353 354 chargeLoyaltyMiles, found := charges.GetByType("loyalty.miles") 355 assert.False(t, found) 356 assert.Equal(t, domain.Price{}, chargeLoyaltyMiles.Price) 357 358 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 359 assert.True(t, found) 360 assert.Equal(t, domain.NewFromInt(100, 1, "EUR"), chargeMain.Price) 361 362 // Test when we pass 15 miles as wish 363 wished := NewWishedToPay().Add("loyalty.miles", domain.NewFromInt(15, 1, "Miles")) 364 charges = p.GetLoyaltyChargeSplit(nil, &wished, 1) 365 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 366 assert.True(t, found) 367 368 assert.Equal(t, domain.NewFromInt(100, 1, "EUR"), chargeMain.Price) 369 370 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 371 assert.False(t, found) 372 assert.Equal(t, domain.Price{}, chargeLoyaltyMiles.Price) 373 374 // Test when we pass 100 miles as wish 375 wished = NewWishedToPay().Add("loyalty.miles", domain.NewFromInt(100, 1, "Miles")) 376 charges = p.GetLoyaltyChargeSplit(nil, &wished, 1) 377 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 378 assert.True(t, found) 379 380 assert.Equal(t, domain.NewFromInt(100, 1, "EUR"), chargeMain.Price) 381 382 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 383 assert.False(t, found) 384 assert.Equal(t, domain.Price{}, chargeLoyaltyMiles.Price) 385 386 // Test when we pass 30 miles as desired payment and wish for qty 2 387 wished = NewWishedToPay().Add("loyalty.miles", domain.NewFromInt(30, 1, "Miles")) 388 doublePrice := p.ActivePrice.GetFinalPrice().Multiply(2) 389 charges = p.GetLoyaltyChargeSplit(&doublePrice, &wished, 1) 390 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 391 assert.True(t, found) 392 393 assert.Equal(t, domain.NewFromInt(200, 1, "EUR"), chargeMain.Price) 394 395 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 396 assert.False(t, found) 397 assert.Equal(t, domain.Price{}, chargeLoyaltyMiles.Price) 398 }) 399 } 400 401 func TestSaleable_GetLoyaltyChargeSplitWithAdjustedValue(t *testing.T) { 402 403 p := Saleable{ 404 ActivePrice: PriceInfo{ 405 // 100EUR value 406 Default: domain.NewFromInt(100, 1, "EUR"), 407 }, 408 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 409 Type: "loyalty.miles", 410 MaxPointsToSpent: nil, 411 // 10 is the minimum to pay in miles (=20EUR value) 412 MinPointsToSpent: *new(big.Float).SetInt64(10), 413 // 50 miles == 100EUR meaning 1Mile = 2EUR 414 Default: domain.NewFromInt(50, 1, "Miles"), 415 }, 416 } 417 418 // we need to pay 150EUR (e,g. because some tax are added) 419 newValue := domain.NewFromInt(150, 1, "EUR") 420 charges := p.GetLoyaltyChargeSplit(&newValue, nil, 1) 421 422 chargeLoyaltyMiles, found := charges.GetByType("loyalty.miles") 423 assert.True(t, found) 424 assert.Equal(t, domain.NewFromInt(10, 1, "Miles"), chargeLoyaltyMiles.Price, "only minimum points expected") 425 426 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 427 assert.True(t, found) 428 assert.Equal(t, domain.NewFromInt(130, 1, "EUR"), chargeMain.Price) 429 430 // pay 150 - and want to spend less then min 431 newValue = domain.NewFromInt(150, 1, "EUR") 432 wished := NewWishedToPay() 433 wished.Add("loyalty.miles", domain.NewFromInt(8, 1, "Miles")) 434 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 1) 435 436 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 437 assert.True(t, found) 438 assert.Equal(t, domain.NewFromInt(10, 1, "Miles"), chargeLoyaltyMiles.Price, "only minimum points expected") 439 440 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 441 assert.True(t, found) 442 assert.Equal(t, domain.NewFromInt(130, 1, "EUR"), chargeMain.Price) 443 444 // we need to pay 50EUR (e,g. because some discounts are applied) 445 newValue = domain.NewFromInt(50, 1, "EUR") 446 charges = p.GetLoyaltyChargeSplit(&newValue, nil, 1) 447 448 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 449 assert.True(t, found) 450 assert.Equal(t, domain.NewFromInt(10, 1, "Miles"), chargeLoyaltyMiles.Price, "only minimum points expected") 451 452 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 453 assert.True(t, found) 454 assert.Equal(t, domain.NewFromInt(30, 1, "EUR"), chargeMain.Price) 455 456 // we need to pay 150EUR and wish to pay everything with miles 457 newValue = domain.NewFromInt(150, 1, "EUR") 458 459 wished = NewWishedToPay() 460 wished.Add("loyalty.miles", domain.NewFromInt(200000, 1, "Miles")) 461 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 1) 462 463 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 464 assert.True(t, found) 465 assert.Equal(t, domain.NewFromInt(75, 1, "Miles"), chargeLoyaltyMiles.Price, "adjusted points expected as charge (not more than total value)") 466 467 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 468 assert.True(t, found) 469 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price) 470 471 } 472 473 func TestSaleable_GetLoyaltyChargeSplitCentRoundingCheck(t *testing.T) { 474 t.Parallel() 475 476 t.Run("9.99 EUR and 53 Miles", func(t *testing.T) { 477 t.Parallel() 478 479 p := Saleable{ 480 ActivePrice: PriceInfo{ 481 // 100EUR value 482 Default: domain.NewFromFloat(9.99, "EUR"), 483 }, 484 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 485 Type: "loyalty.miles", 486 MaxPointsToSpent: nil, 487 // 10 is the minimum to pay in miles (=20EUR value) 488 MinPointsToSpent: *new(big.Float).SetInt64(10), 489 // 50 miles == 100EUR meaning 1Mile = 2EUR 490 Default: domain.NewFromInt(53, 1, "Miles"), // one mile = 5.305305305305305 EUR 491 }, 492 } 493 494 wishedMax := NewWishedToPay() 495 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 496 497 // 107.06 would be 567.98 miles - so we pay 568 miles we expect to pay everything in miles. 498 expectedMilesMax := int64(568) 499 newValue := domain.NewFromFloat(107.06, "EUR") 500 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 501 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 502 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "107.06 expected to be 568 miles") 503 504 wished := NewWishedToPay() 505 wished.Add("loyalty.miles", domain.NewFromInt(expectedMilesMax, 1, "Miles")) 506 507 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 1) 508 509 chargeLoyaltyMiles, found := charges.GetByType("loyalty.miles") 510 assert.True(t, found) 511 assert.Equal(t, domain.NewFromInt(568, 1, "Miles"), chargeLoyaltyMiles.Price, "adjusted points expected as charge (not more than total value)") 512 assert.Equal(t, 107.06, chargeLoyaltyMiles.Value.FloatAmount(), "adjusted points expected as charge (not more than total value)") 513 514 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 515 assert.True(t, found) 516 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price) 517 518 // 106.89 would be 567.084084084084084 miles - so we also pay 567 miles (rounded) we expect to pay everything in miles. 519 newValue = domain.NewFromFloat(106.89, "EUR") 520 521 wished = NewWishedToPay() 522 wished.Add("loyalty.miles", domain.NewFromInt(567, 1, "Miles")) 523 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 1) 524 525 chargeLoyaltyMiles, found = charges.GetByType("loyalty.miles") 526 assert.True(t, found) 527 assert.Equal(t, domain.NewFromInt(567, 1, "Miles"), chargeLoyaltyMiles.Price, "adjusted points expected as charge (not more than total value)") 528 assert.Equal(t, 106.89, chargeLoyaltyMiles.Value.FloatAmount(), "adjusted points expected as charge (not more than total value)") 529 530 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 531 assert.True(t, found) 532 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price) 533 }) 534 535 t.Run("10 items by 9.50 EUR and 53 Miles", func(t *testing.T) { 536 t.Parallel() 537 538 p := Saleable{ 539 ActivePrice: PriceInfo{ 540 Default: domain.NewFromFloat(9.50, "EUR"), 541 }, 542 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 543 Type: "loyalty.miles", 544 MaxPointsToSpent: nil, 545 MinPointsToSpent: *new(big.Float).SetInt64(10), 546 Default: domain.NewFromInt(53, 1, "Miles"), // one mile = 5.57894737 EUR 547 }, 548 } 549 550 wishedMax := NewWishedToPay() 551 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 552 553 // 107.06 would be 567.98 miles - so we pay 568 miles we expect to pay everything in miles. 554 expectedMilesMax := int64(530) 555 newValue := domain.NewFromFloat(95, "EUR") 556 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 10) 557 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 558 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "95 expected to be 530 miles") 559 560 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 561 assert.True(t, found) 562 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price) 563 }) 564 565 t.Run("10 items by 9.49 EUR and 53 Miles", func(t *testing.T) { 566 t.Parallel() 567 568 p := Saleable{ 569 ActivePrice: PriceInfo{ 570 Default: domain.NewFromFloat(9.49, "EUR"), 571 }, 572 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 573 Type: "loyalty.miles", 574 MaxPointsToSpent: nil, 575 MinPointsToSpent: *new(big.Float).SetInt64(10), 576 Default: domain.NewFromInt(53, 1, "Miles"), // one mile = 5.58482613 EUR 577 }, 578 } 579 580 wishedMax := NewWishedToPay() 581 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 582 583 expectedMilesMax := int64(530) 584 newValue := domain.NewFromFloat(94.9, "EUR") 585 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 10) 586 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 587 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "94.9 expected to be 530 miles") 588 589 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 590 assert.True(t, found) 591 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price) 592 }) 593 594 t.Run("item for 1.50 EUR or 450 Miles", func(t *testing.T) { 595 t.Parallel() 596 597 p := Saleable{ 598 ActivePrice: PriceInfo{ 599 Default: domain.NewFromFloat(1.50, "EUR"), 600 }, 601 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 602 Type: "loyalty.miles", 603 MaxPointsToSpent: nil, 604 MinPointsToSpent: *new(big.Float).SetInt64(30), 605 Default: domain.NewFromInt(450, 1, "Miles"), 606 }, 607 } 608 609 wishedMax := NewWishedToPay() 610 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 611 612 expectedMilesMax := int64(450) 613 newValue := domain.NewFromFloat(1.5, "EUR") 614 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 615 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 616 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.5 expected to be 450 miles") 617 618 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 619 require.True(t, found) 620 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 621 622 expectedMilesMax = int64(300) 623 newValue = domain.NewFromFloat(1.00, "EUR") 624 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 625 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 626 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 300 miles") 627 628 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 629 assert.True(t, found) 630 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 631 632 expectedMilesMax = int64(900) 633 newValue = domain.NewFromFloat(3.00, "EUR") // reduced price still calculated perfectly 634 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 3) 635 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 636 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "3 expected to be 900 miles") 637 638 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 639 assert.True(t, found) 640 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 641 642 expectedMiles := int64(90) 643 644 wished := NewWishedToPay() 645 wished.Add("loyalty.miles", domain.NewZero("Miles")) 646 647 newValue = domain.NewFromFloat(4.50, "EUR") 648 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 649 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 650 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 90 Miles") 651 652 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 653 assert.True(t, found) 654 assert.Equal(t, domain.NewFromFloat(4.200000123, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 4.20, but got %f", chargeMain.Price.FloatAmount())) 655 }) 656 657 t.Run("item for 1.49 EUR or 447 Miles", func(t *testing.T) { 658 t.Parallel() 659 660 p := Saleable{ 661 ActivePrice: PriceInfo{ 662 Default: domain.NewFromFloat(1.49, "EUR"), 663 }, 664 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 665 Type: "loyalty.miles", 666 MaxPointsToSpent: nil, 667 MinPointsToSpent: *new(big.Float).SetInt64(30), 668 Default: domain.NewFromInt(447, 1, "Miles"), 669 }, 670 } 671 672 wishedMax := NewWishedToPay() 673 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 674 675 // 1.49 would be 447 miles - so we pay 447 miles we expect to pay everything in miles. 676 expectedMilesMax := int64(447) 677 newValue := domain.NewFromFloat(1.49, "EUR") 678 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 679 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 680 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.49 expected to be 447 miles") 681 682 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 683 require.True(t, found) 684 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 685 686 expectedMilesMax = int64(300) 687 newValue = domain.NewFromFloat(1.00, "EUR") 688 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 689 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 690 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 300 miles") 691 692 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 693 assert.True(t, found) 694 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 695 696 expectedMilesMax = int64(1341) 697 newValue = domain.NewFromFloat(4.47, "EUR") 698 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 699 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 700 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "4.47 expected to be 1341 miles") 701 702 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 703 assert.True(t, found) 704 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 705 706 expectedMiles := int64(90) 707 708 wished := NewWishedToPay() 709 wished.Add("loyalty.miles", domain.NewZero("Miles")) 710 711 newValue = domain.NewFromFloat(4.00, "EUR") 712 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 713 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 714 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 90 Miles") 715 716 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 717 assert.True(t, found) 718 assert.Equal(t, domain.NewFromFloat(3.70000123, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 3.7, but got %f", chargeMain.Price.FloatAmount())) 719 }) 720 721 t.Run("item for 1.01 EUR or 303 Miles", func(t *testing.T) { 722 t.Parallel() 723 724 p := Saleable{ 725 ActivePrice: PriceInfo{ 726 Default: domain.NewFromFloat(1.01, "EUR"), 727 }, 728 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 729 Type: "loyalty.miles", 730 MaxPointsToSpent: nil, 731 MinPointsToSpent: *new(big.Float).SetInt64(30), 732 Default: domain.NewFromInt(303, 1, "Miles"), 733 }, 734 } 735 736 wishedMax := NewWishedToPay() 737 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 738 739 // 1.01 would be 303 miles - so we pay 303 miles we expect to pay everything in miles. 740 expectedMilesMax := int64(303) 741 newValue := domain.NewFromFloat(1.01, "EUR") 742 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 743 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 744 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.01 expected to be 303 miles") 745 746 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 747 require.True(t, found) 748 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 749 750 // 1.0 would be 300 miles - so we pay 300 miles we expect to pay everything in miles. 751 expectedMilesMax = int64(300) 752 newValue = domain.NewFromFloat(1.00, "EUR") 753 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 754 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 755 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 300 miles") 756 757 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 758 assert.True(t, found) 759 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 760 761 expectedMilesMax = int64(909) 762 newValue = domain.NewFromFloat(3.03, "EUR") 763 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 764 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 765 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "3.03 expected to be 909 miles") 766 767 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 768 assert.True(t, found) 769 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 770 771 expectedMiles := int64(90) 772 773 wished := NewWishedToPay() 774 wished.Add("loyalty.miles", domain.NewZero("Miles")) 775 776 newValue = domain.NewFromFloat(3.03, "EUR") 777 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 778 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 779 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 90 Miles") 780 781 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 782 assert.True(t, found) 783 assert.Equal(t, domain.NewFromFloat(2.729999999, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 2.73, but got %f", chargeMain.Price.FloatAmount())) 784 }) 785 786 t.Run("item for 1.99 EUR or 597 Miles", func(t *testing.T) { 787 t.Parallel() 788 789 p := Saleable{ 790 ActivePrice: PriceInfo{ 791 Default: domain.NewFromFloat(1.99, "EUR"), 792 }, 793 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 794 Type: "loyalty.miles", 795 MaxPointsToSpent: nil, 796 MinPointsToSpent: *new(big.Float).SetInt64(30), 797 Default: domain.NewFromInt(597, 1, "Miles"), // 300 miles = 1 EUR 798 }, 799 } 800 801 wishedMax := NewWishedToPay() 802 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 803 804 // 1.99 would be 597 miles - so we pay 597 miles we expect to pay everything in miles. 805 expectedMilesMax := int64(597) 806 newValue := domain.NewFromFloat(1.99, "EUR") 807 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 808 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 809 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.99 expected to be 597 miles") 810 811 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 812 require.True(t, found) 813 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 814 815 expectedMilesMax = int64(300) 816 newValue = domain.NewFromFloat(1.00, "EUR") 817 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 818 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 819 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 300 miles") 820 821 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 822 assert.True(t, found) 823 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 824 825 expectedMilesMax = int64(1791) 826 newValue = domain.NewFromFloat(5.97, "EUR") 827 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 828 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 829 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "5.97 expected to be 1791 miles") 830 831 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 832 assert.True(t, found) 833 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 834 835 expectedMiles := int64(90) 836 837 wished := NewWishedToPay() 838 wished.Add("loyalty.miles", domain.NewZero("Miles")) 839 840 newValue = domain.NewFromFloat(5.97, "EUR") 841 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 842 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 843 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 90 Miles") 844 845 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 846 assert.True(t, found) 847 assert.Equal(t, domain.NewFromFloat(5.6666666, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 5.67, but got %f", chargeMain.Price.FloatAmount())) 848 }) 849 850 t.Run("item for 1.50 EUR or 450 Miles with max 90", func(t *testing.T) { 851 t.Parallel() 852 853 p := Saleable{ 854 ActivePrice: PriceInfo{ 855 Default: domain.NewFromFloat(1.50, "EUR"), 856 }, 857 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 858 Type: "loyalty.miles", 859 MaxPointsToSpent: new(big.Float).SetInt64(90), 860 MinPointsToSpent: *new(big.Float).SetInt64(30), 861 Default: domain.NewFromInt(450, 1, "Miles"), 862 }, 863 } 864 865 wishedMax := NewWishedToPay() 866 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) // we wish to pay everything in miles 867 868 expectedMilesMax := int64(90) 869 newValue := domain.NewFromFloat(1.5, "EUR") 870 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 871 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 872 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount()) 873 874 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 875 require.True(t, found) 876 assert.Equal(t, domain.NewFromFloat(1.20, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should be left to pay 1.20 but got %f", chargeMain.Price.FloatAmount())) 877 878 expectedMilesMax = int64(90) 879 newValue = domain.NewFromFloat(1.00, "EUR") 880 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 881 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 882 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount()) 883 884 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 885 assert.True(t, found) 886 assert.Equal(t, domain.NewFromFloat(0.70, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should be left to pay 1.20 but got %f", chargeMain.Price.FloatAmount())) 887 888 expectedMilesMax = int64(270) 889 newValue = domain.NewFromFloat(3.00, "EUR") // reduced price still calculated perfectly 890 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 3) 891 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 892 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount()) 893 894 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 895 assert.True(t, found) 896 assert.Equal(t, domain.NewFromFloat(2.10, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should be left to pay 2.10 but got %f", chargeMain.Price.FloatAmount())) 897 }) 898 899 t.Run("item for 1.50 EUR or 2 Miles", func(t *testing.T) { 900 t.Parallel() 901 902 p := Saleable{ 903 ActivePrice: PriceInfo{ 904 Default: domain.NewFromFloat(1.50, "EUR"), 905 }, 906 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 907 Type: "loyalty.miles", 908 MaxPointsToSpent: nil, 909 MinPointsToSpent: *new(big.Float).SetInt64(1), 910 //Default: domain.NewFromInt(2, 1, "Miles"), // Should come unrounded!!! 911 Default: domain.NewFromFloat(1.5, "Miles"), 912 }, 913 } 914 915 wishedMax := NewWishedToPay() 916 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 917 918 expectedMilesMax := int64(2) 919 newValue := domain.NewFromFloat(1.5, "EUR") 920 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 921 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 922 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.5 expected to be 2 miles") 923 924 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 925 require.True(t, found) 926 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 927 928 expectedMilesMax = int64(1) 929 newValue = domain.NewFromFloat(1.00, "EUR") 930 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 931 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 932 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 1 mile") 933 934 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 935 assert.True(t, found) 936 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 937 938 expectedMilesMax = int64(5) 939 newValue = domain.NewFromFloat(4.50, "EUR") 940 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 3) 941 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 942 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "4.50 expected to be 5 miles") 943 944 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 945 assert.True(t, found) 946 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 947 948 expectedMiles := int64(3) 949 950 wished := NewWishedToPay() 951 wished.Add("loyalty.miles", domain.NewZero("Miles")) 952 953 newValue = domain.NewFromFloat(4.10, "EUR") 954 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 955 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 956 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 3 Miles") 957 958 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 959 assert.True(t, found) 960 assert.Equal(t, domain.NewFromFloat(1.10, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 1.10, but got %f", chargeMain.Price.FloatAmount())) 961 }) 962 963 t.Run("item for 1.49 EUR or 1 Mile", func(t *testing.T) { 964 t.Parallel() 965 966 p := Saleable{ 967 ActivePrice: PriceInfo{ 968 Default: domain.NewFromFloat(1.49, "EUR"), 969 }, 970 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 971 Type: "loyalty.miles", 972 MaxPointsToSpent: nil, 973 MinPointsToSpent: *new(big.Float).SetInt64(1), 974 //Default: domain.NewFromInt(447, 1, "Miles"), // Should come unrounded!!! 975 Default: domain.NewFromFloat(1.49, "Miles"), 976 }, 977 } 978 979 wishedMax := NewWishedToPay() 980 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 981 982 expectedMilesMax := int64(1) 983 newValue := domain.NewFromFloat(1.49, "EUR") 984 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 985 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 986 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.49 expected to be 1 miles") 987 988 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 989 require.True(t, found) 990 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 991 992 expectedMilesMax = int64(1) 993 newValue = domain.NewFromFloat(1.00, "EUR") 994 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 995 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 996 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 300 miles") 997 998 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 999 assert.True(t, found) 1000 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1001 1002 expectedMilesMax = int64(4) 1003 newValue = domain.NewFromFloat(4.47, "EUR") 1004 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 3) 1005 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1006 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "4.47 expected to be 4 miles") 1007 1008 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1009 assert.True(t, found) 1010 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1011 1012 expectedMiles := int64(3) 1013 1014 wished := NewWishedToPay() 1015 wished.Add("loyalty.miles", domain.NewZero("Miles")) 1016 1017 newValue = domain.NewFromFloat(3.47, "EUR") 1018 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 1019 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1020 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 3 Miles") 1021 1022 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1023 assert.True(t, found) 1024 assert.Equal(t, domain.NewZero("EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("we should gift 0.47 cents in this case, but got %f to pay", chargeMain.Price.FloatAmount())) 1025 }) 1026 1027 t.Run("item for 1.01 EUR or 1 Mile", func(t *testing.T) { 1028 t.Parallel() 1029 1030 p := Saleable{ 1031 ActivePrice: PriceInfo{ 1032 Default: domain.NewFromFloat(1.01, "EUR"), 1033 }, 1034 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 1035 Type: "loyalty.miles", 1036 MaxPointsToSpent: nil, 1037 MinPointsToSpent: *new(big.Float).SetInt64(1), 1038 //Default: domain.NewFromInt(303, 1, "Miles"), // Should come unrounded!!! 1039 Default: domain.NewFromFloat(1.01, "Miles"), 1040 }, 1041 } 1042 1043 wishedMax := NewWishedToPay() 1044 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 1045 1046 expectedMilesMax := int64(1) 1047 newValue := domain.NewFromFloat(1.01, "EUR") 1048 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 1049 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 1050 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.01 expected to be 1 mile") 1051 1052 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 1053 require.True(t, found) 1054 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1055 1056 expectedMilesMax = int64(1) 1057 newValue = domain.NewFromFloat(1.00, "EUR") 1058 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 1059 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1060 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 1 mile") 1061 1062 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1063 assert.True(t, found) 1064 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1065 1066 expectedMilesMax = int64(3) 1067 newValue = domain.NewFromFloat(3.03, "EUR") 1068 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 3) 1069 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1070 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "3.03 expected to be 3 miles") 1071 1072 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1073 assert.True(t, found) 1074 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1075 1076 expectedMiles := int64(3) 1077 1078 wished := NewWishedToPay() 1079 wished.Add("loyalty.miles", domain.NewZero("Miles")) 1080 1081 newValue = domain.NewFromFloat(3.50, "EUR") 1082 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 1083 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1084 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 3 Miles") 1085 1086 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1087 assert.True(t, found) 1088 assert.Equal(t, domain.NewFromFloat(0.50, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 0.50, but got %f", chargeMain.Price.FloatAmount())) 1089 }) 1090 1091 t.Run("item for 1.99 EUR or 2 Miles", func(t *testing.T) { 1092 t.Parallel() 1093 1094 p := Saleable{ 1095 ActivePrice: PriceInfo{ 1096 Default: domain.NewFromFloat(1.99, "EUR"), 1097 }, 1098 ActiveLoyaltyPrice: &LoyaltyPriceInfo{ 1099 Type: "loyalty.miles", 1100 MaxPointsToSpent: nil, 1101 MinPointsToSpent: *new(big.Float).SetInt64(1), 1102 Default: domain.NewFromFloat(1.99, "Miles"), 1103 }, 1104 } 1105 1106 wishedMax := NewWishedToPay() 1107 wishedMax.Add("loyalty.miles", domain.NewFromInt(math.MaxInt64, 1, "Miles")) 1108 1109 expectedMilesMax := int64(2) 1110 newValue := domain.NewFromFloat(1.99, "EUR") 1111 charges := p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 1112 chargeLoyaltyMiles, _ := charges.GetByType("loyalty.miles") 1113 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1.01 expected to be 2 miles") 1114 1115 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 1116 require.True(t, found) 1117 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1118 1119 expectedMilesMax = int64(1) 1120 newValue = domain.NewFromFloat(1.00, "EUR") 1121 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 1) 1122 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1123 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "1 expected to be 1 mile") 1124 1125 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1126 assert.True(t, found) 1127 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1128 1129 expectedMilesMax = int64(6) 1130 newValue = domain.NewFromFloat(5.97, "EUR") 1131 charges = p.GetLoyaltyChargeSplit(&newValue, &wishedMax, 3) 1132 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1133 assert.Equal(t, domain.NewFromInt(expectedMilesMax, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "5.97 expected to be 6 miles") 1134 1135 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1136 assert.True(t, found) 1137 assert.Equal(t, domain.NewFromInt(0, 1, "EUR"), chargeMain.Price, "should be nothing left to pay") 1138 1139 expectedMiles := int64(3) 1140 1141 wished := NewWishedToPay() 1142 wished.Add("loyalty.miles", domain.NewZero("Miles")) 1143 1144 newValue = domain.NewFromFloat(5.97, "EUR") 1145 charges = p.GetLoyaltyChargeSplit(&newValue, &wished, 3) 1146 chargeLoyaltyMiles, _ = charges.GetByType("loyalty.miles") 1147 assert.Equal(t, domain.NewFromInt(expectedMiles, 1, "Miles").FloatAmount(), chargeLoyaltyMiles.Price.FloatAmount(), "you cannot pay less then 3 Miles") 1148 1149 chargeMain, found = charges.GetByType(domain.ChargeTypeMain) 1150 assert.True(t, found) 1151 assert.Equal(t, domain.NewFromFloat(2.97, "EUR").GetPayable(), chargeMain.Price.GetPayable(), fmt.Sprintf("should de left to pay 2.97, but got %f", chargeMain.Price.FloatAmount())) 1152 }) 1153 } 1154 1155 func TestSaleable_GetLoyaltyChargeSplitIgnoreMin(t *testing.T) { 1156 1157 p := Saleable{ 1158 ActivePrice: PriceInfo{ 1159 // 100EUR value 1160 Default: domain.NewFromInt(100, 1, "EUR"), 1161 }, 1162 LoyaltyPrices: []LoyaltyPriceInfo{ 1163 { 1164 Type: "loyalty.miles", 1165 MaxPointsToSpent: new(big.Float).SetInt64(50), 1166 // 10 is the minimum to pay in miles (=20EUR value) 1167 MinPointsToSpent: *new(big.Float).SetInt64(10), 1168 // 50 miles == 100EUR meaning 1Mile = 2EUR 1169 Default: domain.NewFromInt(50, 1, "Miles"), 1170 }, 1171 }, 1172 } 1173 1174 // Test default charges (the min price in points should be evaluated) 1175 charges := p.GetLoyaltyChargeSplitIgnoreMin(nil, nil, 1) 1176 1177 _, found := charges.GetByType("loyalty.miles") 1178 assert.False(t, found) 1179 1180 chargeMain, found := charges.GetByType(domain.ChargeTypeMain) 1181 assert.True(t, found) 1182 assert.Equal(t, domain.NewFromInt(100, 1, "EUR"), chargeMain.Price) 1183 } 1184 1185 func TestSaleable_GetLoyaltyEarningByType(t *testing.T) { 1186 1187 tests := []struct { 1188 name string 1189 loyaltyEarnings []LoyaltyEarningInfo 1190 leType string 1191 wantBool bool 1192 wantEarning *LoyaltyEarningInfo 1193 }{ 1194 { 1195 name: "empty loyalty info", 1196 loyaltyEarnings: nil, 1197 leType: "dontCare", 1198 wantBool: false, 1199 wantEarning: nil, 1200 }, 1201 { 1202 name: "matching loyalty info", 1203 loyaltyEarnings: []LoyaltyEarningInfo{ 1204 { 1205 Type: "MilesAndMore", 1206 Default: domain.NewFromFloat(23.23, "NZD"), 1207 }, 1208 { 1209 Type: "TheOtherThing", 1210 Default: domain.NewFromFloat(24.24, "NZD"), 1211 }, 1212 }, 1213 leType: "MilesAndMore", 1214 wantBool: true, 1215 wantEarning: &LoyaltyEarningInfo{ 1216 Type: "MilesAndMore", 1217 Default: domain.NewFromFloat(23.23, "NZD"), 1218 }, 1219 }, 1220 { 1221 name: "no matching loyalty info", 1222 loyaltyEarnings: []LoyaltyEarningInfo{ 1223 { 1224 Type: "MilesAndMoreX", 1225 Default: domain.NewFromFloat(23.23, "NZD"), 1226 }, 1227 { 1228 Type: "TheOtherThing", 1229 Default: domain.NewFromFloat(24.24, "NZD"), 1230 }, 1231 }, 1232 leType: "MilesAndMore", 1233 wantBool: false, 1234 wantEarning: nil, 1235 }, 1236 } 1237 1238 for _, tt := range tests { 1239 saleable := new(Saleable) 1240 saleable.LoyaltyEarnings = tt.loyaltyEarnings 1241 1242 resultEarning, resultBool := saleable.GetLoyaltyEarningByType(tt.leType) 1243 assert.Equal(t, tt.wantBool, resultBool, tt.name) 1244 assert.Equal(t, tt.wantEarning, resultEarning, tt.name) 1245 } 1246 1247 } 1248 1249 func TestBasicProductData_IsInStockForDeliveryCode(t *testing.T) { 1250 t.Parallel() 1251 1252 t.Run("when in stock for delivery code then return true", func(t *testing.T) { 1253 t.Parallel() 1254 1255 product := BasicProductData{ 1256 Stock: []Stock{ 1257 { 1258 InStock: true, 1259 DeliveryCode: "test", 1260 }, 1261 { 1262 InStock: false, 1263 DeliveryCode: "not this one", 1264 }, 1265 }, 1266 } 1267 1268 result := product.IsInStockForDeliveryCode("test") 1269 1270 assert.True(t, result) 1271 }) 1272 1273 t.Run("when not in stock for delivery code then return false", func(t *testing.T) { 1274 t.Parallel() 1275 1276 product := BasicProductData{ 1277 Stock: []Stock{ 1278 { 1279 InStock: true, 1280 DeliveryCode: "not this one", 1281 }, 1282 { 1283 InStock: false, 1284 DeliveryCode: "test", 1285 }, 1286 }, 1287 } 1288 1289 result := product.IsInStockForDeliveryCode("test") 1290 1291 assert.False(t, result) 1292 }) 1293 1294 t.Run("when delivery code not found return false", func(t *testing.T) { 1295 t.Parallel() 1296 1297 product := BasicProductData{ 1298 Stock: []Stock{ 1299 { 1300 InStock: true, 1301 DeliveryCode: "not this one", 1302 }, 1303 { 1304 InStock: false, 1305 DeliveryCode: "nope", 1306 }, 1307 }, 1308 } 1309 1310 result := product.IsInStockForDeliveryCode("test") 1311 1312 assert.False(t, result) 1313 }) 1314 } 1315 1316 func TestSaleable_GetLoyaltyPriceByType(t *testing.T) { 1317 t.Parallel() 1318 1319 t.Run("returns loyalty from active loyalty price", func(t *testing.T) { 1320 t.Parallel() 1321 1322 activeLoyaltyPrice := LoyaltyPriceInfo{ 1323 Type: "valid_type", 1324 Default: domain.NewFromFloat(10.00, "LOYALTY"), 1325 } 1326 1327 availablePrice1 := LoyaltyPriceInfo{ 1328 Type: "valid_type", 1329 Default: domain.NewFromFloat(5.00, "LOYALTY"), 1330 } 1331 1332 availablePrice2 := LoyaltyPriceInfo{ 1333 Type: "invalid_type", 1334 Default: domain.NewFromFloat(100.00, "LOYALTY"), 1335 } 1336 1337 saleable := Saleable{ 1338 ActiveLoyaltyPrice: &activeLoyaltyPrice, 1339 LoyaltyPrices: []LoyaltyPriceInfo{ 1340 availablePrice1, 1341 availablePrice2, 1342 }, 1343 } 1344 1345 resultPrice, found := saleable.GetLoyaltyPriceByType("valid_type") 1346 require.True(t, found) 1347 assert.Equal(t, activeLoyaltyPrice, *resultPrice) 1348 }) 1349 1350 t.Run("returns loyalty from available loyalty prices", func(t *testing.T) { 1351 t.Parallel() 1352 1353 activeLoyaltyPrice := LoyaltyPriceInfo{ 1354 Type: "invalid_type", 1355 Default: domain.NewFromFloat(10.00, "LOYALTY"), 1356 } 1357 1358 availablePrice1 := LoyaltyPriceInfo{ 1359 Type: "valid_type", 1360 Default: domain.NewFromFloat(5.00, "LOYALTY"), 1361 } 1362 1363 availablePrice2 := LoyaltyPriceInfo{ 1364 Type: "invalid_type", 1365 Default: domain.NewFromFloat(100.00, "LOYALTY"), 1366 } 1367 1368 saleable := Saleable{ 1369 ActiveLoyaltyPrice: &activeLoyaltyPrice, 1370 LoyaltyPrices: []LoyaltyPriceInfo{ 1371 availablePrice1, 1372 availablePrice2, 1373 }, 1374 } 1375 1376 resultPrice, found := saleable.GetLoyaltyPriceByType("valid_type") 1377 require.True(t, found) 1378 assert.Equal(t, availablePrice1, *resultPrice) 1379 }) 1380 1381 t.Run("returns loyalty from available loyalty prices when active is nil", func(t *testing.T) { 1382 t.Parallel() 1383 1384 availablePrice1 := LoyaltyPriceInfo{ 1385 Type: "valid_type", 1386 Default: domain.NewFromFloat(5.00, "LOYALTY"), 1387 } 1388 1389 availablePrice2 := LoyaltyPriceInfo{ 1390 Type: "invalid_type", 1391 Default: domain.NewFromFloat(100.00, "LOYALTY"), 1392 } 1393 1394 saleable := Saleable{ 1395 ActiveLoyaltyPrice: nil, 1396 LoyaltyPrices: []LoyaltyPriceInfo{ 1397 availablePrice1, 1398 availablePrice2, 1399 }, 1400 } 1401 1402 resultPrice, found := saleable.GetLoyaltyPriceByType("valid_type") 1403 require.True(t, found) 1404 assert.Equal(t, availablePrice1, *resultPrice) 1405 }) 1406 1407 t.Run("returns loyalty from available loyalty prices", func(t *testing.T) { 1408 t.Parallel() 1409 1410 activeLoyaltyPrice := LoyaltyPriceInfo{ 1411 Type: "invalid_type", 1412 Default: domain.NewFromFloat(10.00, "LOYALTY"), 1413 } 1414 1415 availablePrice1 := LoyaltyPriceInfo{ 1416 Type: "invalid_type", 1417 Default: domain.NewFromFloat(5.00, "LOYALTY"), 1418 } 1419 1420 availablePrice2 := LoyaltyPriceInfo{ 1421 Type: "invalid_type", 1422 Default: domain.NewFromFloat(100.00, "LOYALTY"), 1423 } 1424 1425 saleable := Saleable{ 1426 ActiveLoyaltyPrice: &activeLoyaltyPrice, 1427 LoyaltyPrices: []LoyaltyPriceInfo{ 1428 availablePrice1, 1429 availablePrice2, 1430 }, 1431 } 1432 1433 resultPrice, found := saleable.GetLoyaltyPriceByType("valid_type") 1434 require.False(t, found) 1435 assert.Nil(t, resultPrice) 1436 }) 1437 }