flamingo.me/flamingo-commerce/v3@v3.11.0/sourcing/domain/service_test.go (about) 1 package domain_test 2 3 import ( 4 "context" 5 "errors" 6 "testing" 7 8 "flamingo.me/flamingo/v3/framework/flamingo" 9 10 "flamingo.me/flamingo-commerce/v3/cart/domain/cart" 11 "flamingo.me/flamingo-commerce/v3/cart/domain/decorator" 12 productDomain "flamingo.me/flamingo-commerce/v3/product/domain" 13 "flamingo.me/flamingo-commerce/v3/sourcing/domain" 14 15 "github.com/stretchr/testify/assert" 16 ) 17 18 type ( 19 availableSourcesProviderMock struct { 20 Sources []domain.Source 21 Error error 22 } 23 stockProviderMock struct { 24 Qty int 25 Error error 26 } 27 stockBySourceAndProductProviderMock struct { 28 // [source.LocationCode][product.Identifier] = Qty 29 Qty map[string]map[string]int 30 Error error 31 } 32 ) 33 34 var ( 35 _ domain.AvailableSourcesProvider = new(availableSourcesProviderMock) 36 _ domain.StockProvider = new(stockProviderMock) 37 _ domain.AvailableSourcesProvider = new(stockBySourceAndProductProviderMock) 38 ) 39 40 func (a availableSourcesProviderMock) GetPossibleSources(_ context.Context, _ productDomain.BasicProduct, _ *cart.DeliveryInfo) ([]domain.Source, error) { 41 return a.Sources, a.Error 42 } 43 44 func (s stockProviderMock) GetStock(_ context.Context, _ productDomain.BasicProduct, _ domain.Source, _ *cart.DeliveryInfo) (int, error) { 45 return s.Qty, s.Error 46 } 47 48 func (s stockBySourceAndProductProviderMock) GetStock(_ context.Context, product productDomain.BasicProduct, source domain.Source, _ *cart.DeliveryInfo) (int, error) { 49 return s.Qty[source.LocationCode][product.GetIdentifier()], s.Error 50 } 51 52 func (s stockBySourceAndProductProviderMock) GetPossibleSources(_ context.Context, _ productDomain.BasicProduct, _ *cart.DeliveryInfo) ([]domain.Source, error) { 53 panic("implement me") 54 } 55 56 func TestDefaultSourcingService_GetAvailableSources(t *testing.T) { 57 t.Run("error handling on unbound providers", func(t *testing.T) { 58 sourcingService := domain.DefaultSourcingService{} 59 sourcingService.Inject(flamingo.NullLogger{}, nil) 60 _, err := sourcingService.GetAvailableSources(context.Background(), nil, nil, nil) 61 assert.EqualError(t, err, "no Source Provider bound", "received error if available sources provider and stock provider are not configured") 62 63 sourcingService = newDefaultSourcingService(nil, nil) 64 _, err = sourcingService.GetAvailableSources(context.Background(), productDomain.SimpleProduct{}, nil, nil) 65 assert.EqualError(t, err, "no Stock Provider bound", "received error if stock provider is not set") 66 }) 67 68 t.Run("error handing on error fetching available sources", func(t *testing.T) { 69 sourcingService := domain.DefaultSourcingService{} 70 sourcingService.Inject(flamingo.NullLogger{}, &struct { 71 AvailableSourcesProvider domain.AvailableSourcesProvider `inject:",optional"` 72 StockProvider domain.StockProvider `inject:",optional"` 73 }{ 74 AvailableSourcesProvider: availableSourcesProviderMock{ 75 Sources: nil, 76 Error: errors.New("mocked available sources provider error"), 77 }, 78 StockProvider: stockProviderMock{}, 79 }) 80 81 _, err := sourcingService.GetAvailableSources(context.Background(), productDomain.SimpleProduct{Identifier: "example"}, nil, nil) 82 assert.Contains(t, err.Error(), "mocked available sources provider error", "result contains the error message of the available sources provider") 83 }) 84 85 t.Run("full qty with nil cart", func(t *testing.T) { 86 stubbedSources := []domain.Source{{LocationCode: "loc1"}} 87 stubbedStockQty := 10 88 89 stockProviderMock := stockProviderMock{Qty: stubbedStockQty} 90 sourcingService := newDefaultSourcingService(stockProviderMock, stubbedSources) 91 92 sources, err := sourcingService.GetAvailableSources(context.Background(), productDomain.SimpleProduct{Identifier: "simple_test"}, nil, nil) 93 assert.NoError(t, err) 94 95 expectedSources := domain.AvailableSourcesPerProduct{domain.ProductID("simple_test"): domain.AvailableSources{ 96 stubbedSources[0]: stubbedStockQty, 97 }} 98 99 assert.Equal(t, expectedSources, sources) 100 }) 101 102 t.Run("qty reduced with existing cart", func(t *testing.T) { 103 stubbedSources := []domain.Source{{LocationCode: "loc1"}} 104 stubbedStockQty := 10 105 stubbedQtyAlreadyInCart := 2 106 stubbedProduct := productDomain.SimpleProduct{Identifier: "productid"} 107 108 testCart := decorator.DecoratedCart{ 109 DecoratedDeliveries: []decorator.DecoratedDelivery{ 110 { 111 DecoratedItems: []decorator.DecoratedCartItem{ 112 { 113 Product: stubbedProduct, 114 Item: cart.Item{Qty: stubbedQtyAlreadyInCart, ID: "item1"}, 115 }, 116 }, 117 }, 118 }, 119 } 120 121 stockProviderMock := stockProviderMock{Qty: stubbedStockQty} 122 sourcingService := newDefaultSourcingService(stockProviderMock, stubbedSources) 123 124 sources, err := sourcingService.GetAvailableSources(context.Background(), stubbedProduct, nil, &testCart) 125 assert.NoError(t, err) 126 127 expectedSources := domain.AvailableSourcesPerProduct{domain.ProductID("productid"): domain.AvailableSources{ 128 stubbedSources[0]: stubbedStockQty - stubbedQtyAlreadyInCart, 129 }} 130 assert.Equal(t, expectedSources, sources) 131 }) 132 133 t.Run("all available qty is already in cart", func(t *testing.T) { 134 stubbedSources := []domain.Source{{LocationCode: "loc1"}, {LocationCode: "loc2"}} 135 stubbedStockQty := 5 136 stubbedQtyAlreadyInCart := 10 137 stubbedProduct := productDomain.SimpleProduct{ 138 Identifier: "marketPlaceCode1", 139 } 140 testCart := decorator.DecoratedCart{ 141 DecoratedDeliveries: []decorator.DecoratedDelivery{ 142 { 143 DecoratedItems: []decorator.DecoratedCartItem{ 144 { 145 Product: stubbedProduct, 146 Item: cart.Item{ 147 ID: "itemID1", 148 Qty: stubbedQtyAlreadyInCart, 149 }, 150 }, 151 }, 152 }, 153 }, 154 } 155 stockProviderMock := stockProviderMock{Qty: stubbedStockQty} 156 sourcingService := newDefaultSourcingService(stockProviderMock, stubbedSources) 157 158 availableSources, err := sourcingService.GetAvailableSources(context.Background(), stubbedProduct, nil, &testCart) 159 160 t.Log(availableSources) 161 162 assert.Error(t, err) 163 assert.ErrorIs(t, err, domain.ErrNoSourceAvailable) 164 }) 165 166 t.Run("get sources for bundle product, cart is nil, sources shouldn't be allocated", func(t *testing.T) { 167 t.Parallel() 168 169 simpleInBundle1 := productDomain.SimpleProduct{Identifier: "product1"} 170 simpleInBundle2 := productDomain.SimpleProduct{Identifier: "product2"} 171 172 bundleProduct := productDomain.BundleProductWithActiveChoices{ 173 BundleProduct: productDomain.BundleProduct{ 174 Identifier: "bundle_product", 175 }, 176 ActiveChoices: map[productDomain.Identifier]productDomain.ActiveChoice{ 177 "identifier1": { 178 Qty: 1, 179 Product: simpleInBundle1, 180 }, 181 "identifier2": { 182 Qty: 2, 183 Product: simpleInBundle2, 184 }, 185 }, 186 } 187 188 source1 := domain.Source{LocationCode: "Source1"} 189 source2 := domain.Source{LocationCode: "Source2"} 190 stubbedSources := []domain.Source{source1, source2} 191 192 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 193 Qty: map[string]map[string]int{ 194 "Source1": { 195 "product1": 6, 196 "product2": 4, 197 }, 198 "Source2": { 199 "product1": 4, 200 "product2": 1, 201 }, 202 }, 203 } 204 205 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 206 207 availableSources, err := sourcingService.GetAvailableSources(context.Background(), bundleProduct, nil, nil) 208 assert.NoError(t, err) 209 210 assert.Equal(t, 10, availableSources[domain.ProductID(simpleInBundle1.GetIdentifier())].QtySum()) 211 assert.Equal(t, 5, availableSources[domain.ProductID(simpleInBundle2.GetIdentifier())].QtySum()) 212 }) 213 214 t.Run("get sources for simple product, cart is nil, sources shouldn't be allocated", func(t *testing.T) { 215 t.Parallel() 216 217 simpleProduct := productDomain.SimpleProduct{ 218 Identifier: "product2", 219 } 220 221 source1 := domain.Source{LocationCode: "Source1"} 222 source2 := domain.Source{LocationCode: "Source2"} 223 stubbedSources := []domain.Source{source1, source2} 224 225 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 226 Qty: map[string]map[string]int{ 227 "Source1": { 228 "product1": 6, 229 "product2": 4, 230 }, 231 "Source2": { 232 "product1": 4, 233 "product2": 1, 234 }, 235 }, 236 } 237 238 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 239 240 availableSources, err := sourcingService.GetAvailableSources(context.Background(), simpleProduct, nil, nil) 241 assert.NoError(t, err) 242 243 assert.Equal(t, 5, availableSources[domain.ProductID(simpleProduct.GetIdentifier())].QtySum()) 244 }) 245 246 t.Run("get sources for simple product, cart is not nil, sources should be allocated", func(t *testing.T) { 247 t.Parallel() 248 249 simpleProduct := productDomain.SimpleProduct{ 250 Identifier: "product2", 251 } 252 253 source1 := domain.Source{LocationCode: "Source1"} 254 source2 := domain.Source{LocationCode: "Source2"} 255 stubbedSources := []domain.Source{source1, source2} 256 257 testCart := decorator.DecoratedCart{ 258 DecoratedDeliveries: []decorator.DecoratedDelivery{ 259 { 260 DecoratedItems: []decorator.DecoratedCartItem{ 261 { 262 Product: simpleProduct, 263 Item: cart.Item{Qty: 2, ID: "item2"}, 264 }, 265 }, 266 }, 267 { 268 DecoratedItems: []decorator.DecoratedCartItem{ 269 { 270 Product: simpleProduct, 271 Item: cart.Item{Qty: 1, ID: "item2"}, 272 }, 273 }, 274 }, 275 }, 276 } 277 278 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 279 Qty: map[string]map[string]int{ 280 "Source1": { 281 "product1": 5, 282 "product2": 4, 283 }, 284 "Source2": { 285 "product1": 5, 286 "product2": 1, 287 }, 288 }, 289 } 290 291 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 292 293 availableSources, err := sourcingService.GetAvailableSources(context.Background(), simpleProduct, nil, &testCart) 294 assert.NoError(t, err) 295 296 assert.Equal(t, 3, availableSources[domain.ProductID(simpleProduct.GetIdentifier())].QtySum()) 297 }) 298 299 t.Run("get sources for bundle product, cart is not nil, sources should be allocated", func(t *testing.T) { 300 t.Parallel() 301 302 simpleInBundle1 := productDomain.SimpleProduct{Identifier: "gucciSlippers"} 303 simple2 := productDomain.SimpleProduct{Identifier: "gucciTShirt"} 304 305 bundleProduct := productDomain.BundleProductWithActiveChoices{ 306 BundleProduct: productDomain.BundleProduct{ 307 Identifier: "bundle_product", 308 }, 309 ActiveChoices: map[productDomain.Identifier]productDomain.ActiveChoice{ 310 "identifier1": { 311 Qty: 1, 312 Product: simpleInBundle1, 313 }, 314 "identifier2": { 315 Qty: 1, 316 Product: simple2, 317 }, 318 }, 319 } 320 321 testCart := decorator.DecoratedCart{ 322 DecoratedDeliveries: []decorator.DecoratedDelivery{ 323 { 324 DecoratedItems: []decorator.DecoratedCartItem{ 325 { 326 Product: bundleProduct, 327 Item: cart.Item{Qty: 2, ID: "item1"}, 328 }, 329 { 330 Product: simple2, 331 Item: cart.Item{Qty: 1, ID: "item2"}, 332 }, 333 }, 334 }, 335 { 336 DecoratedItems: []decorator.DecoratedCartItem{ 337 { 338 Product: simple2, 339 Item: cart.Item{Qty: 1, ID: "item3"}, 340 }, 341 }, 342 }, 343 }, 344 } 345 346 source1 := domain.Source{LocationCode: "Source1"} 347 source2 := domain.Source{LocationCode: "Source2"} 348 stubbedSources := []domain.Source{source1, source2} 349 350 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 351 Qty: map[string]map[string]int{ 352 "Source1": { 353 "gucciSlippers": 5, 354 "gucciTShirt": 4, 355 }, 356 "Source2": { 357 "gucciSlippers": 5, 358 "gucciTShirt": 1, 359 }, 360 }, 361 } 362 363 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 364 365 availableSources, err := sourcingService.GetAvailableSources(context.Background(), bundleProduct, nil, &testCart) 366 assert.NoError(t, err) 367 368 assert.Equal(t, 8, availableSources[domain.ProductID(simpleInBundle1.GetIdentifier())].QtySum()) 369 assert.Equal(t, 1, availableSources[domain.ProductID(simple2.GetIdentifier())].QtySum()) 370 }) 371 } 372 373 func TestDefaultSourcingService_AllocateItems(t *testing.T) { 374 t.Parallel() 375 /** 376 Given: 377 Cart: 378 Delivery1: 379 product1 - 10 380 product2 - 5 381 Delivery2: 382 product1 - 5 383 384 existing Stock Source : 385 Source1: 386 product1: 8 387 product2: 3 388 Source2: 389 product1: 10 390 Source3: 391 product2: 10 392 393 => Expected Result: 394 395 Cart: 396 Delivery1: 397 item1:product1 - 10 398 sourced: Source1 -> 8 & Source2 -> 2 399 item2:product2 - 5 400 sourced: Source1 -> 3 & Source3 -> 2 401 Delivery2: 402 item3: product1 - 5 403 sourced: Source2 -> 5 404 405 */ 406 t.Run("allocate easy", func(t *testing.T) { 407 t.Parallel() 408 409 stubbedProduct1 := productDomain.SimpleProduct{Identifier: "product1"} 410 stubbedProduct2 := productDomain.SimpleProduct{Identifier: "product2"} 411 412 testCart := decorator.DecoratedCart{ 413 DecoratedDeliveries: []decorator.DecoratedDelivery{ 414 { 415 DecoratedItems: []decorator.DecoratedCartItem{ 416 { 417 Product: stubbedProduct1, 418 Item: cart.Item{Qty: 10, ID: "item1"}, 419 }, 420 { 421 Product: stubbedProduct2, 422 Item: cart.Item{Qty: 5, ID: "item2"}, 423 }, 424 }, 425 }, 426 { 427 DecoratedItems: []decorator.DecoratedCartItem{ 428 { 429 Product: stubbedProduct1, 430 Item: cart.Item{Qty: 5, ID: "item3"}, 431 }, 432 }, 433 }, 434 }, 435 } 436 437 source1 := domain.Source{LocationCode: "Source1"} 438 source2 := domain.Source{LocationCode: "Source2"} 439 source3 := domain.Source{LocationCode: "Source3"} 440 stubbedSources := []domain.Source{source1, source2, source3} 441 442 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 443 Qty: map[string]map[string]int{ 444 "Source1": { 445 "product1": 8, 446 "product2": 3, 447 }, 448 "Source2": { 449 "product1": 10, 450 }, 451 "Source3": { 452 "product2": 10, 453 }, 454 }, 455 } 456 457 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 458 459 itemAllocation, err := sourcingService.AllocateItems(context.Background(), &testCart) 460 assert.NoError(t, err) 461 assert.NoError(t, itemAllocation[domain.ItemID("item1")].Error) 462 assert.Len(t, itemAllocation[domain.ItemID("item1")].AllocatedQtys[domain.ProductID(stubbedProduct1.GetIdentifier())], 2) 463 464 assert.Equal(t, 8, itemAllocation[domain.ItemID("item1")].AllocatedQtys[domain.ProductID(stubbedProduct1.GetIdentifier())][source1]) 465 assert.Equal(t, 2, itemAllocation[domain.ItemID("item1")].AllocatedQtys[domain.ProductID(stubbedProduct1.GetIdentifier())][source2]) 466 assert.Equal(t, 3, itemAllocation[domain.ItemID("item2")].AllocatedQtys[domain.ProductID(stubbedProduct2.GetIdentifier())][source1]) 467 assert.Equal(t, 2, itemAllocation[domain.ItemID("item2")].AllocatedQtys[domain.ProductID(stubbedProduct2.GetIdentifier())][source3]) 468 assert.Equal(t, 5, itemAllocation[domain.ItemID("item3")].AllocatedQtys[domain.ProductID(stubbedProduct1.GetIdentifier())][source2]) 469 }) 470 471 t.Run("allocate cart with bundle item", func(t *testing.T) { 472 t.Parallel() 473 474 bundleProduct := productDomain.BundleProductWithActiveChoices{ 475 BundleProduct: productDomain.BundleProduct{ 476 Identifier: "bundle_product", 477 }, 478 ActiveChoices: map[productDomain.Identifier]productDomain.ActiveChoice{ 479 "identifier1": { 480 Qty: 1, 481 Product: productDomain.SimpleProduct{ 482 Identifier: "product4", 483 }, 484 }, 485 "identifier2": { 486 Qty: 2, 487 Product: productDomain.SimpleProduct{ 488 Identifier: "product3", 489 }, 490 }, 491 }, 492 } 493 494 simple2 := productDomain.SimpleProduct{Identifier: "product2"} 495 simple5 := productDomain.SimpleProduct{Identifier: "product5"} 496 497 testCart := decorator.DecoratedCart{ 498 DecoratedDeliveries: []decorator.DecoratedDelivery{ 499 { 500 DecoratedItems: []decorator.DecoratedCartItem{ 501 { 502 Product: bundleProduct, 503 Item: cart.Item{Qty: 1, ID: "item1"}, 504 }, 505 { 506 Product: simple2, 507 Item: cart.Item{Qty: 1, ID: "item2"}, 508 }, 509 }, 510 }, 511 { 512 DecoratedItems: []decorator.DecoratedCartItem{ 513 { 514 Product: simple5, 515 Item: cart.Item{Qty: 4, ID: "item3"}, 516 }, 517 }, 518 }, 519 }, 520 } 521 522 source1 := domain.Source{LocationCode: "Source1"} 523 source2 := domain.Source{LocationCode: "Source2"} 524 source3 := domain.Source{LocationCode: "Source3"} 525 stubbedSources := []domain.Source{source1, source2, source3} 526 527 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 528 Qty: map[string]map[string]int{ 529 "Source1": { 530 "product2": 3, 531 "product4": 27, 532 "product5": 3, 533 }, 534 "Source2": { 535 "product1": 10, 536 "product4": 27, 537 }, 538 "Source3": { 539 "product2": 10, 540 "product3": 4, 541 }, 542 }, 543 } 544 545 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 546 547 itemAllocation, err := sourcingService.AllocateItems(context.Background(), &testCart) 548 assert.NoError(t, err) 549 assert.NoError(t, itemAllocation[domain.ItemID("item1")].Error) 550 assert.NoError(t, itemAllocation[domain.ItemID("item2")].Error) 551 552 assert.ErrorIs(t, domain.ErrInsufficientSourceQty, itemAllocation[domain.ItemID("item3")].Error) 553 554 assert.Equal(t, 1, 555 itemAllocation[domain.ItemID("item1")].AllocatedQtys["product4"][source1]) 556 assert.Equal(t, 2, 557 itemAllocation[domain.ItemID("item1")].AllocatedQtys["product3"][source3]) 558 assert.Equal(t, 1, 559 itemAllocation[domain.ItemID("item2")].AllocatedQtys[domain.ProductID(simple2.GetIdentifier())][source1]) 560 }) 561 562 t.Run("if too many products are allocated to a bundle, they won't be available for the next item", func(t *testing.T) { 563 t.Parallel() 564 565 simpleInBundle1 := productDomain.SimpleProduct{Identifier: "product1"} 566 simple2 := productDomain.SimpleProduct{Identifier: "product2"} 567 568 bundleProduct := productDomain.BundleProductWithActiveChoices{ 569 BundleProduct: productDomain.BundleProduct{ 570 Identifier: "bundle_product", 571 }, 572 ActiveChoices: map[productDomain.Identifier]productDomain.ActiveChoice{ 573 "identifier1": { 574 Qty: 1, 575 Product: simpleInBundle1, 576 }, 577 "identifier2": { 578 Qty: 2, 579 Product: simple2, 580 }, 581 }, 582 } 583 584 testCart := decorator.DecoratedCart{ 585 DecoratedDeliveries: []decorator.DecoratedDelivery{ 586 { 587 DecoratedItems: []decorator.DecoratedCartItem{ 588 { 589 Product: bundleProduct, 590 Item: cart.Item{Qty: 2, ID: "item1"}, 591 }, 592 { 593 Product: simple2, 594 Item: cart.Item{Qty: 1, ID: "item2"}, 595 }, 596 }, 597 }, 598 }, 599 } 600 601 source1 := domain.Source{LocationCode: "Source1"} 602 source2 := domain.Source{LocationCode: "Source2"} 603 stubbedSources := []domain.Source{source1, source2} 604 605 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 606 Qty: map[string]map[string]int{ 607 "Source1": { 608 "product1": 28, 609 "product2": 4, 610 }, 611 "Source2": { 612 "product1": 28, 613 "product2": 0, 614 }, 615 }, 616 } 617 618 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 619 620 itemAllocation, err := sourcingService.AllocateItems(context.Background(), &testCart) 621 assert.NoError(t, err) 622 assert.NoError(t, itemAllocation[domain.ItemID("item1")].Error) 623 624 assert.ErrorIs(t, domain.ErrInsufficientSourceQty, itemAllocation[domain.ItemID("item2")].Error) 625 626 assert.Equal(t, 2, 627 itemAllocation[domain.ItemID("item1")].AllocatedQtys["product1"][source1]) 628 assert.Equal(t, 4, 629 itemAllocation[domain.ItemID("item1")].AllocatedQtys["product2"][source1]) 630 }) 631 632 t.Run("if an item from a bundle is purchased separately and insufficient quantity remains, it won't be available for the bundle", func(t *testing.T) { 633 t.Parallel() 634 635 simpleInBundle1 := productDomain.SimpleProduct{Identifier: "product1"} 636 simple2 := productDomain.SimpleProduct{Identifier: "product2", Saleable: productDomain.Saleable{IsSaleable: true}} 637 638 bundleProduct := productDomain.BundleProductWithActiveChoices{ 639 BundleProduct: productDomain.BundleProduct{ 640 Identifier: "bundle_product", 641 }, 642 ActiveChoices: map[productDomain.Identifier]productDomain.ActiveChoice{ 643 "identifier1": { 644 Qty: 1, 645 Product: simpleInBundle1, 646 }, 647 "identifier2": { 648 Qty: 3, 649 Product: simple2, 650 }, 651 }, 652 } 653 654 testCart := decorator.DecoratedCart{ 655 DecoratedDeliveries: []decorator.DecoratedDelivery{ 656 { 657 DecoratedItems: []decorator.DecoratedCartItem{ 658 { 659 Product: simple2, 660 Item: cart.Item{Qty: 3, ID: "item1"}, 661 }, 662 { 663 Product: bundleProduct, 664 Item: cart.Item{Qty: 1, ID: "item2"}, 665 }, 666 }, 667 }, 668 }, 669 } 670 671 source1 := domain.Source{LocationCode: "Source1"} 672 source2 := domain.Source{LocationCode: "Source2"} 673 stubbedSources := []domain.Source{source1, source2} 674 675 stockBySourceAndProductProviderMock := stockBySourceAndProductProviderMock{ 676 Qty: map[string]map[string]int{ 677 "Source1": { 678 "product1": 28, 679 "product2": 3, 680 }, 681 "Source2": { 682 "product1": 28, 683 "product2": 0, 684 }, 685 }, 686 } 687 688 sourcingService := newDefaultSourcingService(stockBySourceAndProductProviderMock, stubbedSources) 689 690 itemAllocation, err := sourcingService.AllocateItems(context.Background(), &testCart) 691 assert.NoError(t, err) 692 assert.NoError(t, itemAllocation[domain.ItemID("item1")].Error) 693 694 assert.ErrorIs(t, domain.ErrInsufficientSourceQty, itemAllocation[domain.ItemID("item2")].Error) 695 696 assert.Equal(t, 3, 697 itemAllocation[domain.ItemID("item1")].AllocatedQtys[domain.ProductID(simple2.GetIdentifier())][source1]) 698 }) 699 } 700 701 func newDefaultSourcingService(stockProvider domain.StockProvider, expectedSources []domain.Source) domain.DefaultSourcingService { 702 sourcingService := domain.DefaultSourcingService{} 703 availableSourcesProviderMock := availableSourcesProviderMock{Sources: expectedSources} 704 705 sourcingService.Inject(flamingo.NullLogger{}, &struct { 706 AvailableSourcesProvider domain.AvailableSourcesProvider `inject:",optional"` 707 StockProvider domain.StockProvider `inject:",optional"` 708 }{ 709 StockProvider: stockProvider, 710 AvailableSourcesProvider: availableSourcesProviderMock, 711 }) 712 713 return sourcingService 714 }