Newer
Older
Warehouse / tests / Application.IntegrationTests / _testdep / TestDataFactory.cs
@Derek Comartin Derek Comartin on 22 Aug 2023 5 KB Init
using MyWarehouse.Domain.Common.ValueObjects.Mass;
using MyWarehouse.Domain.Common.ValueObjects.Money;
using MyWarehouse.Domain.Partners;
using MyWarehouse.Domain.Products;
using MyWarehouse.Domain.Transactions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyWarehouse.Application.IntegrationTests
{
    public class TestDataFactory
    {
        private readonly TestData _data;

        public TestDataFactory(TestData data) => _data = data;

        /// <summary>
        /// Creates the requested number of products, and adds them to the database.
        /// </summary>
        /// <param name="productOverrides">Action to invoke on each product created, to allow property customization before saving.</param>
        /// <returns>The list of created products.</returns>
        public async Task<List<Product>> AddProducts(int howMany, Action<(int index, Product product)> productOverrides = null)
        {
            var products = new Product[howMany];
            for (int i = 0; i < howMany; i++)
            {
                var product = new Product(
                    name: "Product" + i,
                    description: "Description" + i,
                    price: new Money((i+1) * 10, Currency.USD),
                    mass: new Mass(i+1, MassUnit.Kilogram));

                productOverrides?.Invoke((i, product));
                products[i] = product;
            }

            await _data.AddRangeAsync(products);
            return products.ToList();
        }

        /// <summary>
        /// Creates the requested number of partners, and adds them to the database.
        /// </summary>
        /// <param name="partnerOverrides">Action to invoke on each partner created, to allow property customization before saving.</param>
        /// <returns>The list of created partners.</returns>
        public async Task<List<Partner>> AddPartners(int howMany, Action<(int index, Partner partner)> partnerOverrides = null)
        {
            var partners = new Partner[howMany];
            for (int i = 0; i < howMany; i++)
            {
                var partner = new Partner(
                    name: "Partner Number" + (i+1),
                    address: new Address(
                        country: "Canada",
                        city: "Toronto",
                        street: (i+1) + " Test street",
                        zipcode: string.Join("", Enumerable.Range(i+1, 6)).Substring(0, 6))
                    );

                partnerOverrides?.Invoke((i, partner));
                partners[i] = partner;
            }

            await _data.AddRangeAsync(partners);
            return partners.ToList();
        }

        /// <summary>
        /// Creates a procurement transaction.
        /// </summary>
        /// <param name="partner">The partner to create a transaction with. If not provided, a new partner will be created.</param>
        /// <param name="createNewProducts">If true, new products will be created for all transaction lines. If false, products will be read from database.</param>
        public async Task<Transaction> CreateProcurementTransaction(int numberOfLines, Partner partner = null, bool createNewProducts = true)
        {
            partner ??= (await AddPartners(1)).First();

            var products = createNewProducts
                ? await AddProducts(numberOfLines)
                : await _data.GetAllAsync<Product>();

            if (products.Count < numberOfLines)
                throw new ArgumentException($"Cannot create {numberOfLines} transaction lines, " +
                    $"because there are only {products.Count} products. " +
                    $"You can set '{createNewProducts}' to true to create the necessary products.", nameof(numberOfLines));

            int quantity = 1;
            var lines = products.Take(numberOfLines).Select(p => (p, quantity++)).ToList();

            var transaction = partner.ProcureFrom(lines);
            await _data.AddAsync(transaction);

            return transaction;
        }

        public async Task<Transaction> CreateSalesTransaction(Partner partner, IEnumerable<Product> productsToSell, float ratioOfStockToSell = 1f)
        {
            if (ratioOfStockToSell <= 0 || ratioOfStockToSell > 1)
                throw new ArgumentException("Ratio must be greater than 0 and less than 1.", nameof(ratioOfStockToSell));

            if (productsToSell.Any(p => p.NumberInStock == 0))
                throw new ArgumentException("All products must have a stock to be able to sell them.", nameof(ratioOfStockToSell));

            var lines = productsToSell.Select(p => (p, (int)Math.Ceiling(p.NumberInStock * ratioOfStockToSell))).ToList();

            var transaction = partner.SellTo(lines);
            await _data.AddAsync(transaction);

            return transaction;
        }

        /// <summary>
        /// Creates a sales transaction by automatically creating first a backing procurement transaction
        /// which in turn creates the necessary products for the transaction.
        /// </summary>
        /// <param name="partner">The partner to create a transaction with. If not provided, a new partner will be created.</param>
        /// <returns>Returns the sales transaction created. The result in the database will be
        /// the added products, the procurement transaction, and the sales transaction.</returns>
        public async Task<Transaction> CreateSalesTransaction(int numberOfLines, Partner partner = null)
        {
            partner ??= (await AddPartners(1)).First();

            var procurement = await CreateProcurementTransaction(numberOfLines, partner);

            var salesTransaction = partner.SellTo(procurement.TransactionLines.Select(p => (p.Product, p.Quantity)));
            await _data.AddAsync(salesTransaction);

            return salesTransaction;
        }
    }
}