From 4cbf95388e01b802fd20069f3af94f9c77f8f5b0 Mon Sep 17 00:00:00 2001 From: Kristian Sylte Date: Tue, 14 Jan 2025 15:33:38 +0100 Subject: [PATCH 1/2] completed --- domain.md | 51 ++++++++++ exercise.main/Basket.cs | 56 +++++++++++ exercise.main/Discount.cs | 137 +++++++++++++++++++++++++++ exercise.main/Program.cs | 26 ++++- exercise.main/Receipt.cs | 49 ++++++++++ exercise.main/Repository.cs | 112 ++++++++++++++++++++++ exercise.main/Store.cs | 58 ++++++++++++ exercise.main/StoreItem.cs | 99 +++++++++++++++++++ exercise.main/User.cs | 52 ++++++++++ exercise.tests/BasketTests.cs | 70 ++++++++++++++ exercise.tests/DiscountTest.cs | 87 +++++++++++++++++ exercise.tests/ReceiptTest.cs | 35 +++++++ exercise.tests/RepositoryTest.cs | 33 +++++++ exercise.tests/StoreItemTests.cs | 49 ++++++++++ exercise.tests/UnitTest1.cs | 40 ++++++-- exercise.tests/UserTest.cs | 82 ++++++++++++++++ exercise.tests/exercise.tests.csproj | 4 + 17 files changed, 1032 insertions(+), 8 deletions(-) create mode 100644 domain.md create mode 100644 exercise.main/Basket.cs create mode 100644 exercise.main/Discount.cs create mode 100644 exercise.main/Receipt.cs create mode 100644 exercise.main/Repository.cs create mode 100644 exercise.main/Store.cs create mode 100644 exercise.main/StoreItem.cs create mode 100644 exercise.main/User.cs create mode 100644 exercise.tests/BasketTests.cs create mode 100644 exercise.tests/DiscountTest.cs create mode 100644 exercise.tests/ReceiptTest.cs create mode 100644 exercise.tests/RepositoryTest.cs create mode 100644 exercise.tests/StoreItemTests.cs create mode 100644 exercise.tests/UserTest.cs diff --git a/domain.md b/domain.md new file mode 100644 index 00000000..44b32f8b --- /dev/null +++ b/domain.md @@ -0,0 +1,51 @@ +# Domain + +## User stories +1. Public: So I can order a bagel before work, I'd like to add a specific type of bagel to my basket. +2. Public: So I can change my order, I'd like to remove a bagel from my basket. +3. Public: So that I can not overfill my small bagel basket I'd like to know when my basket is full when I try adding an item beyond my basket capacity. +4. Public: So that I can maintain my sanity I'd like to know if I try to remove an item that doesn't exist in my basket. +5. Customer: So I know how much money I need, I'd like to know the total cost of items in my basket. +6. Customer: So I know what the damage will be, I'd like to know the cost of a bagel before I add it to my basket. +7. Customer: So I can shake things up a bit, I'd like to be able to choose fillings for my bagel. +8. Customer: So I don't over-spend, I'd like to know the cost of each filling before I add it to my bagel order. +9. Manager: So we don't get any weird requests, I want customers to only be able to order things that we stock in our inventory. +10. Manager: So that I can expand my business, I’d like to change the capacity of baskets. + +## Requirements +* Add bagel to basket +* Remove bagel from basket +* Basket capacity +* Feedback on full basket +* Error on remove non-existing item +* See total cost of basket +* See cost of bagel before adding to basket +* Allow for choosing filling of bagel +* See cost of filling before adding to bagel +* Only allow for ordering items in stock +* Allow for changing basket capacity for admins + + +### Extensions: + +1. Allow for special discounts: 3 for 2 and such +2. Add functionality for receipts to be printed +3. Support discounts in the receipts +4. Send text message confirmation +5. Order by text message +6. See text message history + +## Classes + +* StoreItem +* Basket +* Store +* Receipt +* User? +* Discount +* IRepository +* ListRepository + +StoreItem: productCode: string, name: string, variant: string, price: double; +Basket: items: List + diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs new file mode 100644 index 00000000..f2143975 --- /dev/null +++ b/exercise.main/Basket.cs @@ -0,0 +1,56 @@ +namespace exercise.core; + +public class Basket +{ + private List items = new List(); + public required int Capacity { get; set; } + + public List GetItems() + { + return this.items; + } + + public Receipt Purchase(DiscountContainer discounts) + { + var discounted = discounts.ApplyDiscounts(items); + this.items = new List(); + return new Receipt(discounted); + } + + public double GetTotalPrice(DiscountContainer discounts) + { + var discounted = discounts.ApplyDiscounts(items); + return discounted.Select(i => i.GetPrice()).Sum(); + } + + public bool AddItem(StoreItem item) + { + if (this.items.Count >= this.Capacity) + { + return false; + } + this.items.Add(item); + return true; + } + + public bool RemoveItem(StoreItem storeItem) + { + var found = this.items.Find(i => i.Equals(storeItem)); + if (found == null) + { + return false; + } + this.items.Remove(found); + return true; + } + + public bool UpdateCapacity(int newCapacity) + { + if (newCapacity < this.items.Count) + { + return false; + } + this.Capacity = newCapacity; + return true; + } +} diff --git a/exercise.main/Discount.cs b/exercise.main/Discount.cs new file mode 100644 index 00000000..094a59b8 --- /dev/null +++ b/exercise.main/Discount.cs @@ -0,0 +1,137 @@ +namespace exercise.core; + +public class DiscountContainer +{ + public List discounts = new List(); + + public void AddDiscount(Discount discount) + { + this.discounts.Add(discount); + this.discounts = this.discounts.OrderByDescending(disc => disc.priority).ToList(); + } + + public List ApplyDiscounts(List items) + { + List discounted = new List(); + List nonDiscounted = new List(items); + + foreach (Discount disc in this.discounts) + { + bool applicable = true; + foreach ((Predicate pred, int amount) in disc.DiscountRequirement) + { + if (nonDiscounted.Where(it => pred(it)).Count() < amount) + { + applicable = false; + break; + } + } + + if (applicable) + { + List toBundle = new List(); + foreach ((Predicate pred, int amount) in disc.DiscountRequirement) + { + for (int i = 0; i < amount; i++) + { + var toDiscount = nonDiscounted.Find(pred); + if (toDiscount == null) + { + throw new Exception("oops"); + } + nonDiscounted.Remove(toDiscount); + toBundle.Add(toDiscount); + } + } + discounted.Add(new DiscountBundle(toBundle, disc.newPrice)); + } + } + return nonDiscounted.Concat(discounted).ToList(); + } +} + +public class Discount +{ + public required List<( + Predicate requiredItem, + int requiredAmount + )> DiscountRequirement { get; init; } + public required double newPrice { get; init; } + public required int priority { get; init; } +} + +public class DiscountBundle : StoreItem +{ + private List _storeItems; + private double _oldPrice; + + public DiscountBundle( + string code, + string name, + string variant, + double newPrice, + double oldPrice + ) + : base(code, name, variant, newPrice) + { + this._storeItems = new List(); + this._oldPrice = oldPrice; + } + + public DiscountBundle(List items, double newPrice) + : base("DISC", "Discount", "", newPrice) + { + this._storeItems = items; + this._oldPrice = items.Sum(it => it.GetPrice()); + } + + public override double GetPrice() + { + var nonDiscounted = 0.0; + foreach (StoreItem item in this._storeItems) + { + foreach (StoreItem flat in item.GetItemsFlattened()) + { + if (flat is NonDiscountable) + { + nonDiscounted += flat.GetPrice(); + } + } + } + return this._price + nonDiscounted; + } + + public void AddItem(StoreItem storeItem) + { + this._storeItems.Add(storeItem); + } + + public void SetNewPrice(double newPrice) + { + this._price = newPrice; + } + + public override IReadOnlyCollection GetItemsFlattened() + { + return base.GetItemsFlattened(); + } + + public double GetSavedAmount() + { + return this._oldPrice - this._price; + } + + public override string ToString() + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine(); + + foreach (StoreItem discounted in this._storeItems) + { + sb.Append(discounted.ToString()); + } + sb.AppendLine($" - £({this.GetSavedAmount():F2})"); + sb.AppendLine(); + return sb.ToString(); + } +} diff --git a/exercise.main/Program.cs b/exercise.main/Program.cs index 3751555c..90f8d75c 100644 --- a/exercise.main/Program.cs +++ b/exercise.main/Program.cs @@ -1,2 +1,26 @@ // See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using exercise.core; + +IRepository repo = LocalRepository.Default(); +Store store = new Store(repo); +User bob = new User { UserId = "bob", priv = Privilege.Admin }; +store.AddUser(bob); +store.setActiveUser(bob); +store.ModifyCartCapacity(bob, 10); + +for (int i = 0; i < 8; i++) +{ + var bagel = LocalRepository.Default().getRegisteredItems()[0]; + if (bagel is Bagel bg) + { + var filling = LocalRepository.Default().getRegisteredItems()[9]; + if (filling is BagelFilling f) + { + bg.AddFilling(f); + } + } + store.AddToCart(bagel); +} + +var r = store.Checkout(); +System.Console.WriteLine(r?.GetReceiptText()); diff --git a/exercise.main/Receipt.cs b/exercise.main/Receipt.cs new file mode 100644 index 00000000..b1a7e287 --- /dev/null +++ b/exercise.main/Receipt.cs @@ -0,0 +1,49 @@ +namespace exercise.core; + +public class Receipt +{ + private List _purchasedItems = new List(); + + public Receipt(List items) + { + this._purchasedItems = items; + } + + public string GetReceiptText() + { + var rb = new System.Text.StringBuilder(); + rb.AppendLine(" ~~~ Bob's Bagels ~~~"); + rb.AppendLine(); + rb.AppendLine(DateTime.Now.ToString()); + rb.AppendLine(); + rb.AppendLine("----------------------------"); + rb.AppendLine(); + this._purchasedItems.ForEach(item => rb.AppendLine(item.ToString())); + rb.AppendLine(); + rb.AppendLine("----------------------------"); + rb.AppendLine( + $" Total cost £{this._purchasedItems.Select(i => i.GetPrice()).Sum():F2}" + ); + rb.AppendLine(); + + var savedAmount = 0.0; + foreach (StoreItem storeItem in this._purchasedItems) + { + if (storeItem is DiscountBundle discounted) + { + savedAmount += discounted.GetSavedAmount(); + } + } + rb.AppendLine($"You saved a total of {savedAmount:F2}"); + rb.AppendLine("on this trip"); + + rb.AppendLine("Thank you"); + rb.AppendLine("for your order!"); + return rb.ToString(); + } + + public void PrintReceipt() + { + System.Console.Write(this.GetReceiptText()); + } +} diff --git a/exercise.main/Repository.cs b/exercise.main/Repository.cs new file mode 100644 index 00000000..6ffad7c7 --- /dev/null +++ b/exercise.main/Repository.cs @@ -0,0 +1,112 @@ +namespace exercise.core; + +public interface IRepository +{ + public List getRegisteredItems(); + public DiscountContainer GetDiscountContainer(); + public bool AddUser(User user); + public bool RemoveUser(User user); +} + +public class LocalRepository : IRepository +{ + public required List _items { get; init; } + public required Dictionary _users { get; init; } + public required DiscountContainer _discounts { get; init; } + private User? _activeUser = null; + + public List getRegisteredItems() + { + return this._items; + } + + public bool AddUser(User user) + { + if (this._users.ContainsKey(user.UserId)) + { + return false; + } + this._users.Add(user.UserId, user); + return true; + } + + public bool RemoveUser(User user) + { + if (!this._users.ContainsKey(user.UserId)) + { + return false; + } + this._users.Remove(user.UserId); + return true; + } + + public static LocalRepository Default() + { + var storeItems = new List + { + new Bagel("BGLO", "Bagel", "Onion", 0.49), + new Bagel("BGLP", "Bagel", "Plain", 0.39), + new Bagel("BGLE", "Bagel", "Everything", 0.49), + new Bagel("BGLS", "Bagel", "Sesame", 0.49), + new StoreItem("COFB", "Coffee", "Black", 0.99), + new StoreItem("COFW", "Coffee", "White", 1.19), + new StoreItem("COFC", "Coffee", "Capuccino", 1.29), + new StoreItem("COFL", "Coffee", "Latte", 1.29), + new BagelFilling("FILB", "Filling", "Bacon", 0.12), + new BagelFilling("FILE", "Filling", "Egg", 0.12), + new BagelFilling("FILC", "Filling", "Cheese", 0.12), + new BagelFilling("FILX", "Filling", "Cream Cheese", 0.12), + new BagelFilling("FILX", "Filling", "Smoked Salmon", 0.12), + new BagelFilling("FILH", "Filling", "Ham", 0.12), + }; + + var discounts = new DiscountContainer(); + discounts.AddDiscount( + new Discount + { + newPrice = 2.49, + DiscountRequirement = new List<(Predicate, int)> + { + ((it) => it.ProductCode.Substring(0, 3).ToLower() == "bgl", 6), + }, + priority = 0, + } + ); + discounts.AddDiscount( + new Discount + { + newPrice = 3.99, + DiscountRequirement = new List<(Predicate, int)> + { + ((it) => it.ProductCode.Substring(0, 3).ToLower() == "bgl", 12), + }, + priority = 1, + } + ); + discounts.AddDiscount( + new Discount + { + newPrice = 1.25, + DiscountRequirement = new List<(Predicate, int)> + { + ((it) => it.ProductCode.Substring(0, 3).ToLower() == "bgl", 1), + ((it) => it.ProductCode.Substring(0, 3).ToLower() == "cof", 1), + }, + priority = 2, + } + ); + + var users = new Dictionary(); + return new LocalRepository + { + _items = storeItems, + _users = users, + _discounts = discounts, + }; + } + + public DiscountContainer GetDiscountContainer() + { + return this._discounts; + } +} diff --git a/exercise.main/Store.cs b/exercise.main/Store.cs new file mode 100644 index 00000000..5e2e79f4 --- /dev/null +++ b/exercise.main/Store.cs @@ -0,0 +1,58 @@ +namespace exercise.core; + +// Not tested since it only directs functions to members +public class Store +{ + private IRepository _repository; + private User? _activeUser; + + public Store(IRepository repository) + { + this._repository = repository; + } + + public void AddUser(User user) + { + this._repository.AddUser(user); + } + + public void setActiveUser(User user) + { + this._activeUser = user; + } + + public bool AddToCart(StoreItem item) + { + return this._activeUser?.AddItemToCart(item) ?? false; + } + + public Receipt? Checkout() + { + return this._activeUser?.BuyCart(_repository.GetDiscountContainer()); + } + + public bool RemoveFromCart(StoreItem item) + { + if (this._activeUser == null) + { + return false; + } + return this._activeUser.RemoveItemFromCart(item); + } + + public bool ModifyCartCapacity(User user, int newCapacity) + { + if (this._activeUser == null) + return false; + return user.ModifyCartCapacity(this._activeUser, newCapacity); + } + + public IReadOnlyCollection? GetCartItems() + { + if (this._activeUser == null) + { + return null; + } + return this._activeUser.GetBasketItems(); + } +} diff --git a/exercise.main/StoreItem.cs b/exercise.main/StoreItem.cs new file mode 100644 index 00000000..7b932d0d --- /dev/null +++ b/exercise.main/StoreItem.cs @@ -0,0 +1,99 @@ +namespace exercise.core; + +public class StoreItem +{ + public string ProductCode { get; init; } + public string Name { get; init; } + public string Variant { get; init; } + protected double _price; + + public StoreItem(string code, string name, string variant, double price) + { + if (code.Length != 4) + { + throw new ArgumentException("Product code must have 4 characters"); + } + this.ProductCode = code; + this.Name = name; + this.Variant = variant; + this._price = price; + } + + public virtual IReadOnlyCollection GetItemsFlattened() + { + return new List { this }.AsReadOnly(); + } + + public virtual double GetPrice() + { + return this._price; + } + + public override string ToString() + { + return $"{Variant} {Name} £{_price:F2}"; + } +} + +public class Bagel : StoreItem +{ + private List _fillings = new List(); + + public Bagel(string code, string name, string variant, double price) + : base(code, name, variant, price) + { + if (name != "Bagel") + { + throw new ArgumentException("bagel name must be bagel"); + } + if (code.Substring(0, 3).ToLower() != "bgl") + { + throw new ArgumentException("Bagel SKU must start with bgl"); + } + } + + public override double GetPrice() + { + return this._fillings.Select((filling) => filling.GetPrice()).Sum() + this._price; + } + + public override IReadOnlyCollection GetItemsFlattened() + { + return this._fillings.Concat(new List { this }).ToList().AsReadOnly(); + } + + // Fillings shouldn't count towards basket capacity + public void AddFilling(BagelFilling filling) + { + this._fillings.Add(filling); + } + + public override string ToString() + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine($"{Variant} {Name} £{_price:F2}"); + foreach (BagelFilling filling in this._fillings) + { + sb.AppendLine($" {filling.ToString():F2}"); + } + return sb.ToString(); + } +} + +public interface NonDiscountable { } + +public class BagelFilling : StoreItem, NonDiscountable +{ + public BagelFilling(string code, string name, string variant, double price) + : base(code, name, variant, price) + { + if (name != "Filling") + { + throw new ArgumentException("Filling name must be filling"); + } + if (code.Substring(0, 3).ToLower() != "fil") + { + throw new ArgumentException($"Bagel filling SKU must start with FIL, got {code}"); + } + } +} diff --git a/exercise.main/User.cs b/exercise.main/User.cs new file mode 100644 index 00000000..cf5eb1d9 --- /dev/null +++ b/exercise.main/User.cs @@ -0,0 +1,52 @@ +namespace exercise.core; + +public enum Privilege +{ + User, + Admin, +} + +public class User +{ + public required Privilege priv { get; init; } + public required string UserId { get; init; } + private Basket _basket = new Basket { Capacity = 0 }; + public int BasketCapacity + { + get { return this._basket.Capacity; } + } + + public bool AddItemToCart(StoreItem item) + { + return this._basket.AddItem(item); + } + + public bool ModifyCartCapacity(User adminUser, int newCapacity) + { + if (adminUser.priv != Privilege.Admin) + { + return false; + } + return this._basket.UpdateCapacity(newCapacity); + } + + public bool RemoveItemFromCart(StoreItem item) + { + return this._basket.RemoveItem(item); + } + + public IReadOnlyCollection GetBasketItems() + { + return this._basket.GetItems().AsReadOnly(); + } + + public double GetCartPrice(DiscountContainer discounts) + { + return this._basket.GetTotalPrice(discounts); + } + + public Receipt BuyCart(DiscountContainer discounts) + { + return this._basket.Purchase(discounts); + } +} diff --git a/exercise.tests/BasketTests.cs b/exercise.tests/BasketTests.cs new file mode 100644 index 00000000..3bc3d3d3 --- /dev/null +++ b/exercise.tests/BasketTests.cs @@ -0,0 +1,70 @@ +using exercise.core; + +namespace exercise.tests; + +public class BasketTests +{ + private Basket _basket = new Basket { Capacity = 5 }; + + [SetUp] + public void Setup() + { + this._basket = new Basket { Capacity = 5 }; + } + + [Test] + public void AddRemove() + { + var item = TestUtils.testBagels()[0]; + Assert.That(this._basket.GetItems().Count, Is.EqualTo(0)); + Assert.True(this._basket.AddItem(item)); + Assert.That(this._basket.GetItems().Count, Is.EqualTo(1)); + Assert.True(this._basket.RemoveItem(item)); + Assert.That(this._basket.GetItems().Count, Is.EqualTo(0)); + } + + [Test] + public void UpdateCapacity() + { + Assert.True(this._basket.UpdateCapacity(1)); + Assert.That(this._basket.Capacity, Is.EqualTo(1)); + Assert.True(this._basket.UpdateCapacity(3)); + Assert.That(this._basket.Capacity, Is.EqualTo(3)); + Assert.True(this._basket.AddItem(TestUtils.testBagels()[0])); + Assert.True(this._basket.AddItem(TestUtils.testBagels()[1])); + Assert.False(this._basket.UpdateCapacity(1)); + Assert.That(this._basket.Capacity, Is.EqualTo(3)); + } + + [Test] + public void TotalPrice() + { + // 0.49 + var filledBagel = TestUtils.testBagels()[0]; + // 0.12 + filledBagel.AddFilling(TestUtils.testFillings()[0]); + // 0.99 + var coffee = TestUtils.testItems()[0]; + + this._basket.AddItem(filledBagel); + this._basket.AddItem((coffee)); + Assert.That( + this._basket.GetTotalPrice(new DiscountContainer()), + Is.EqualTo(0.49 + 0.12 + 0.99) + ); + } + + [Test] + public void purchase() + { + var filledBagel = TestUtils.testBagels()[0]; + filledBagel.AddFilling(TestUtils.testFillings()[0]); + var coffee = TestUtils.testItems()[0]; + + this._basket.AddItem(filledBagel); + this._basket.AddItem((coffee)); + + this._basket.Purchase(new DiscountContainer()); + Assert.That(this._basket.GetItems().Count, Is.EqualTo(0)); + } +} diff --git a/exercise.tests/DiscountTest.cs b/exercise.tests/DiscountTest.cs new file mode 100644 index 00000000..0a3b4667 --- /dev/null +++ b/exercise.tests/DiscountTest.cs @@ -0,0 +1,87 @@ +using exercise.core; + +namespace exercise.tests; + +public class DiscountTest +{ + DiscountContainer _discounts = LocalRepository.Default()._discounts; + + [Test] + public void discounts() + { + var items = new List(); + for (int i = 0; i < 6; i++) + { + items.Add(TestUtils.testBagels()[0]); + } + var discounted = _discounts.ApplyDiscounts(items); + Assert.That(discounted.Count(), Is.EqualTo(1)); + Assert.That(discounted[0].GetPrice(), Is.EqualTo(2.49)); + + items = new List(); + for (int i = 0; i < 12; i++) + { + items.Add(TestUtils.testBagels()[0]); + } + discounted = _discounts.ApplyDiscounts(items); + Assert.That(discounted.Count(), Is.EqualTo(1)); + Assert.That(discounted[0].GetPrice(), Is.EqualTo(3.99)); + + items = new List(); + items.Add(TestUtils.testBagels()[0]); + items.Add(TestUtils.testItems()[0]); + discounted = _discounts.ApplyDiscounts(items); + Assert.That(discounted.Count(), Is.EqualTo(1)); + Assert.That(discounted[0].GetPrice(), Is.EqualTo(1.25)); + + items = new List(); + for (int i = 0; i < 18; i++) + { + items.Add(TestUtils.testBagels()[0]); + } + discounted = _discounts.ApplyDiscounts(items); + Assert.That(discounted.Count(), Is.EqualTo(2)); + Assert.That(discounted.Select(dc => dc.GetPrice()).Sum(), Is.EqualTo(3.99 + 2.49)); + } + + [Test] + public void GetSavedAmount() + { + var items = new List(); + for (int i = 0; i < 6; i++) + { + items.Add(TestUtils.testBagels()[0]); + } + var discounted = _discounts.ApplyDiscounts(items)[0]; + if (discounted is DiscountBundle db) + { + Assert.That(db.GetSavedAmount(), Is.EqualTo(0.49 * 6 - 2.49).Within(0.0001)); + } + + items = new List(); + for (int i = 0; i < 12; i++) + { + items.Add(TestUtils.testBagels()[0]); + } + discounted = _discounts.ApplyDiscounts(items)[0]; + if (discounted is DiscountBundle db2) + { + Assert.That(db2.GetSavedAmount(), Is.EqualTo(0.49 * 12 - 3.99).Within(0.0001)); + } + } + + [Test] + public void DontDiscountFilling() + { + var items = new List(); + for (int i = 0; i < 6; i++) + { + var bagel = TestUtils.testBagels()[0]; + bagel.AddFilling(TestUtils.testFillings()[0]); + items.Add(bagel); + } + var discounted = _discounts.ApplyDiscounts(items); + Assert.That(discounted.Count(), Is.EqualTo(1)); + Assert.That(discounted[0].GetPrice(), Is.EqualTo(2.49 + 0.12 * 6)); + } +} diff --git a/exercise.tests/ReceiptTest.cs b/exercise.tests/ReceiptTest.cs new file mode 100644 index 00000000..0bf130ea --- /dev/null +++ b/exercise.tests/ReceiptTest.cs @@ -0,0 +1,35 @@ +using exercise.core; + +namespace exercise.tests; + +public class ReceiptTest +{ + Basket _basket = new Basket { Capacity = 10 }; + + [SetUp] + public void SetUp() + { + this._basket = new Basket { Capacity = 10 }; + } + + [Test] + public void MoreItemsLongerString() + { + List items = new List(); + for (int i = 0; i < 5; i++) + { + items.Add(TestUtils.testItems()[0]); + } + var r1 = new Receipt(items); + + items = new List(); + for (int i = 0; i < 9; i++) + { + items.Add(TestUtils.testItems()[0]); + } + var r2 = new Receipt(items); + var r1Length = r1.GetReceiptText().Count(c => c.Equals('\n')); + var r2Length = r2.GetReceiptText().Count(c => c.Equals('\n')); + Assert.That(r1Length, Is.LessThan(r2Length)); + } +} diff --git a/exercise.tests/RepositoryTest.cs b/exercise.tests/RepositoryTest.cs new file mode 100644 index 00000000..b65e8715 --- /dev/null +++ b/exercise.tests/RepositoryTest.cs @@ -0,0 +1,33 @@ +using exercise.core; + +namespace exercise.tests; + +public class RepositoryTest +{ + private IRepository _repo = LocalRepository.Default(); + + [SetUp] + public void SetUp() + { + _repo = LocalRepository.Default(); + } + + [Test] + public void ContainsRegisteredItems() + { + Assert.That(_repo.getRegisteredItems().Count, Is.EqualTo(14)); + } + + [Test] + public void ContainsDiscounts() + { + Assert.That(_repo.GetDiscountContainer().discounts.Count, Is.EqualTo(3)); + } + + [Test] + public void CantAddDuplicateUser() + { + Assert.True(_repo.AddUser(new User { UserId = "test", priv = Privilege.Admin })); + Assert.False(_repo.AddUser(new User { UserId = "test", priv = Privilege.Admin })); + } +} diff --git a/exercise.tests/StoreItemTests.cs b/exercise.tests/StoreItemTests.cs new file mode 100644 index 00000000..8a694a4b --- /dev/null +++ b/exercise.tests/StoreItemTests.cs @@ -0,0 +1,49 @@ +using exercise.core; + +namespace exercise.tests; + +public class StoreItemTests +{ + public List items = new List(); + public List bagels = new List(); + public List fillings = new List(); + + [SetUp] + public void Setup() + { + var bagels = TestUtils.testBagels(); + var coffees = TestUtils.testItems(); + var fillings = TestUtils.testFillings(); + this.items = coffees; + this.bagels = bagels; + this.fillings = fillings; + } + + [Test] + public void BagelAddFillingGetFlattend() + { + var bagel = this.bagels[0]; + var filling1 = this.fillings[0]; + var filling2 = this.fillings[1]; + Assert.That(bagel.GetPrice(), Is.EqualTo(0.49)); + Assert.That(bagel.GetItemsFlattened().Count, Is.EqualTo(1)); + + bagel.AddFilling(filling1); + Assert.That(bagel.GetPrice(), Is.EqualTo(0.49 + 0.12)); + Assert.That(bagel.GetItemsFlattened().Count, Is.EqualTo(2)); + + bagel.AddFilling(filling2); + Assert.That(bagel.GetPrice(), Is.EqualTo(0.49 + 0.12 + 0.12)); + Assert.That(bagel.GetItemsFlattened().Count, Is.EqualTo(3)); + } + + [Test] + public void Validations() + { + Assert.Throws(() => new StoreItem("TOOLONG", "A", "A", 0.00)); + Assert.Throws(() => new Bagel("NBGL", "Bagel", "A", 0.00)); + Assert.Throws(() => new Bagel("BGLA", "NotBagel", "A", 0.00)); + Assert.Throws(() => new BagelFilling("NFIL", "Filling", "A", 0.00)); + Assert.Throws(() => new BagelFilling("FILB", "NotFilling", "A", 0.00)); + } +} diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 7bdb8968..aba218c1 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -1,15 +1,41 @@ +using exercise.core; + namespace exercise.tests; -public class Tests +public class TestUtils { - [SetUp] - public void Setup() + public static List testItems() + { + return new List + { + new StoreItem("COFB", "Coffee", "Black", 0.99), + new StoreItem("COFW", "Coffee", "White", 1.19), + new StoreItem("COFC", "Coffee", "Capuccino", 1.29), + new StoreItem("COFL", "Coffee", "Latte", 1.29), + }; + } + + public static List testBagels() { + return new List + { + new Bagel("BGLO", "Bagel", "Onion", 0.49), + new Bagel("BGLP", "Bagel", "Plain", 0.39), + new Bagel("BGLE", "Bagel", "Everything", 0.49), + new Bagel("BGLS", "Bagel", "Sesame", 0.49), + }; } - [Test] - public void Test1() + public static List testFillings() { - Assert.Pass(); + return new List + { + new BagelFilling("FILB", "Filling", "Bacon", 0.12), + new BagelFilling("FILE", "Filling", "Egg", 0.12), + new BagelFilling("FILC", "Filling", "Cheese", 0.12), + new BagelFilling("FILX", "Filling", "Cream Cheese", 0.12), + new BagelFilling("FILX", "Filling", "Smoked Salmon", 0.12), + new BagelFilling("FILH", "Filling", "Ham", 0.12), + }; } -} \ No newline at end of file +} diff --git a/exercise.tests/UserTest.cs b/exercise.tests/UserTest.cs new file mode 100644 index 00000000..d0432415 --- /dev/null +++ b/exercise.tests/UserTest.cs @@ -0,0 +1,82 @@ +using exercise.core; + +namespace exercise.tests; + +public class UserTest +{ + private User _user = new User { UserId = "bob", priv = Privilege.User }; + private User _admin = new User { UserId = "ben", priv = Privilege.Admin }; + + [SetUp] + public void Setup() + { + _user = new User { UserId = "bob", priv = Privilege.User }; + _admin = new User { UserId = "ben", priv = Privilege.Admin }; + } + + [Test] + public void ModifyCartCapacity() + { + this._user.ModifyCartCapacity(_admin, 5); + Assert.That(this._user.BasketCapacity, Is.EqualTo(5)); + this._user.ModifyCartCapacity(_admin, 2); + Assert.That(this._user.BasketCapacity, Is.EqualTo(2)); + this._user.AddItemToCart(TestUtils.testItems()[0]); + this._user.AddItemToCart(TestUtils.testItems()[1]); + Assert.False(this._user.ModifyCartCapacity(_admin, 1)); + } + + [Test] + public void AddGetRemoveItem() + { + this._user.ModifyCartCapacity(_admin, 5); + Assert.That(this._user.GetBasketItems(), Is.Empty); + this._user.AddItemToCart(TestUtils.testItems()[0]); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(1)); + this._user.AddItemToCart(TestUtils.testItems()[1]); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(2)); + this._user.RemoveItemFromCart(this._user.GetBasketItems().ElementAt(1)); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(1)); + this._user.RemoveItemFromCart(this._user.GetBasketItems().ElementAt(0)); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(0)); + } + + [Test] + public void AddToFullCart() + { + this._user.ModifyCartCapacity(_admin, 1); + this._user.AddItemToCart(TestUtils.testItems()[0]); + Assert.False(this._user.AddItemToCart(TestUtils.testItems()[1])); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(1)); + } + + [Test] + public void GetCartPrice() + { + this._user.ModifyCartCapacity(_admin, 5); + Assert.That(this._user.GetCartPrice(new DiscountContainer()), Is.EqualTo(0.0)); + // 0.99 + this._user.AddItemToCart(TestUtils.testItems()[0]); + Assert.That(this._user.GetCartPrice(new DiscountContainer()), Is.EqualTo(0.99)); + // 0.49 + var bagel = TestUtils.testBagels()[0]; + // 0.12 + bagel.AddFilling(TestUtils.testFillings()[0]); + this._user.AddItemToCart(bagel); + Assert.That( + this._user.GetCartPrice(new DiscountContainer()), + Is.EqualTo(0.99 + 0.49 + 0.12) + ); + } + + [Test] + public void BuyCart() + { + this._user.ModifyCartCapacity(_admin, 5); + this._user.AddItemToCart(TestUtils.testItems()[0]); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(1)); + var rc = this._user.BuyCart(new DiscountContainer()); + Assert.That(rc, Is.Not.Null); + Assert.That(this._user.GetBasketItems().Count, Is.EqualTo(0)); + } +} diff --git a/exercise.tests/exercise.tests.csproj b/exercise.tests/exercise.tests.csproj index 9fed8e17..a3a97d4f 100644 --- a/exercise.tests/exercise.tests.csproj +++ b/exercise.tests/exercise.tests.csproj @@ -17,4 +17,8 @@ + + + + From ad8de9c638ba373294db102cdcd921386087ea6d Mon Sep 17 00:00:00 2001 From: Kristian Sylte Date: Tue, 14 Jan 2025 15:43:27 +0100 Subject: [PATCH 2/2] added reason for not having a table --- domain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain.md b/domain.md index 44b32f8b..fff89808 100644 --- a/domain.md +++ b/domain.md @@ -46,6 +46,5 @@ * IRepository * ListRepository -StoreItem: productCode: string, name: string, variant: string, price: double; -Basket: items: List +** Instead of making a table, I made empty classes and function stubs before creating any code. **