diff --git a/snippets/1001_MastermindTdd/game.py b/snippets/1001_MastermindTdd/game.py new file mode 100644 index 0000000..fb3d93f --- /dev/null +++ b/snippets/1001_MastermindTdd/game.py @@ -0,0 +1,136 @@ +import random +from logic import tipp_kiszamolasa + +# Elérhető színek +SZINEK = ["szurke", "piros", "narancs", "sarga", "zold", "pink", "lila", "kek", "fekete", "feher"] +MAX_KOROK = 12 + +def welcome(): + """Üdvözlő üzenet és játékszabályok""" + print("=" * 60) + print(" " * 20 + "MASTERMIND JÁTÉK") + print("=" * 60) + print("\nÜdvözöllek a Mastermind játékban!") + print(f"\nElérhető színek ({len(SZINEK)} db):") + for i, szin in enumerate(SZINEK, 1): + print(f" {i}. {szin}", end=" " if i % 3 != 0 else "\n") + print("\n\nCélod: Találd ki a 4 színből álló titkos kombinációt!") + print(f"Neked {MAX_KOROK} körödön van tippelni.\n") + print("Válaszban kapod:") + print(" - FEKETE tüskék: hány szín van jó helyen") + print(" - FEHÉR tüskék: hány szín van rossz helyen\n") + print("Adj meg 4 színt szóközzel elválasztva!") + print("Például: piros kek zold sarga\n") + print("=" * 60 + "\n") + +def generalt_felallas(): + """Véletlenszerű felállás generálása""" + return [random.choice(SZINEK) for _ in range(4)] + +def beker_tippet(kor): + """Tipp bekérése a felhasználótól""" + while True: + try: + print(f"[{kor}. kör] Add meg a tipped: ", end="") + tipp_str = input().strip().lower() + + if not tipp_str: + print("❌ Üres tipp! Próbáld újra.") + continue + + tipp = tipp_str.split() + + if len(tipp) != 4: + print(f"❌ Pontosan 4 színt kell megadnod! Te {len(tipp)} színt adtál meg.") + continue + + # Ellenőrizzük, hogy minden szín érvényes-e + hibas_szinek = [szin for szin in tipp if szin not in SZINEK] + if hibas_szinek: + print(f"❌ Érvénytelen szín(ek): {', '.join(hibas_szinek)}") + print(f" Elérhető színek: {', '.join(SZINEK)}") + continue + + return tipp + except KeyboardInterrupt: + print("\n\n👋 Játék megszakítva. Viszlát!") + exit(0) + except Exception as e: + print(f"❌ Hiba: {e}") + continue + +def eredmeny_kiiras(fekete, feher): + """Eredmény szép megjelenítése""" + print(f" Eredmény: ", end="") + + # Fekete tüskék + if fekete > 0: + print(f"🔴 {fekete} fekete", end="") + + # Fehér tüskék + if feher > 0: + if fekete > 0: + print(", ", end="") + print(f"⚪ {feher} fehér", end="") + + # Ha egyik sincs + if fekete == 0 and feher == 0: + print("❌ Nincs találat", end="") + + print() + +def jatek(): + """Fő játék logika""" + welcome() + + # Titkos felállás generálása + felallas = generalt_felallas() + # print(f"[DEBUG] Titkos kód: {' '.join(felallas)}") # Debug célra + + # Játék ciklus + for kor in range(1, MAX_KOROK + 1): + tipp = beker_tippet(kor) + + try: + fekete, feher = tipp_kiszamolasa(felallas, tipp, SZINEK) + eredmeny_kiiras(fekete, feher) + + # Győzelem ellenőrzése + if fekete == 4: + print("\n" + "=" * 60) + print(f"🎉 GRATULÁLOK! Kitaláltad {kor} körből!") + print(f"A titkos kód: {' '.join(felallas).upper()}") + print("=" * 60) + return True + + print() # Üres sor a következő kör előtt + + except ValueError as e: + print(f"❌ Hiba: {e}") + continue + + # Ha elfogytak a körök + print("\n" + "=" * 60) + print("😞 Sajnos elfogytak a köreid!") + print(f"A titkos kód: {' '.join(felallas).upper()}") + print("=" * 60) + return False + +def main(): + """Főprogram - újrajátszás kezelése""" + while True: + jatek() + + print("\nSzeretnél újra játszani? (i/n): ", end="") + try: + valasz = input().strip().lower() + if valasz not in ['i', 'igen', 'y', 'yes']: + print("\n👋 Köszönöm a játékot! Viszlát!") + break + print("\n" + "=" * 60 + "\n") + except KeyboardInterrupt: + print("\n\n👋 Viszlát!") + break + +if __name__ == "__main__": + main() diff --git a/snippets/1001_MastermindTdd/index.md b/snippets/1001_MastermindTdd/index.md new file mode 100644 index 0000000..8ca4e70 --- /dev/null +++ b/snippets/1001_MastermindTdd/index.md @@ -0,0 +1,48 @@ +--- +layout: post +title: Mastermind fejlesztés Test First módszerrel és AI segítségével +tags: python testfirst mieset +author: Szakszon Ádám Dániel +--- + +# Mastermind Esettanulmány + +## Bevezetés +Ebben a projektben egy klasszikus Mastermind játék motorját készítettem el. A célom az volt, hogy megtanuljam a **Test First** módszertan folyamatát AI (GitHub Copilot és LLM modellek) támogatással, megvizsgálva, hogyan alakítja át az AI a tesztvezérelt megközelítést. + +## Fejlesztési módszertan +A fejlesztés során nem a produkciós kódot írtam meg először, hanem a teljes tesztkészletet határoztam meg a `pytest` keretrendszerben. Ez a gyakorlatban tökéletes specifikációként szolgált az AI számára. + +### Tesztek típusai: +* **Hibaágak:** Érvénytelen színek és rossz tipp-hossz kezelése. +* **Logikai ágak:** Fekete és fehér tüskék számolása, különös tekintettel a duplikált színekre. + +## Algoritmus leírása +A `tipp_kiszamolasa` függvény két lépésben dolgozik: +1. Megkeresi a pontos egyezéseket (fekete tüskék). +2. A maradék színekből kiszámolja a benne lévő, de rossz helyen lévő színeket (fehér tüskék). + +## Érdekes Promptok + +A fejlesztés során az AI-t két lépcsőben irányítottam. Először megadtam neki a teljes tesztkészletet, hogy az alapján generálja le a logikát: + +> "Szia! Kérlek, írd meg a `logic.py` tartalmát a `test_logic.py`-ban található tesztjeim alapján. A feladat a `tipp_kiszamolasa(felallas, tipp, szinek)` függvény megírása. Fontos, hogy a kód minden tesztesetnek megfeleljen." + +Később, amikor az AI túl bonyolult algoritmust (felesleges feltételeket) próbált bevezetni, szükség volt egy pontosító, visszaterelő utasításra: + +> "Térjünk vissza az alapokhoz: először egy ciklussal számoljuk össze az összes pontos egyezést, majd a maradékból határozzuk meg a rossz helyen lévő színeket. Ne bonyolítsd túl a logikát!" + +## Eredmények +A tesztek lefuttatása után az alábbi eredményt kaptam: +![Passed Tests](teszt.png) + +Aztán kértem egy konzolon tesztelhető felületet is az AI-tól: +![Konzol](jatekfelulet.png) + +## Tanulságok: AI asszisztált Test First vs. TDD + +A projekt legfőbb tanulsága a klasszikus **TDD (Test-Driven Development)** és az AI-jal támogatott **Test First** megközelítés közötti markáns különbség megtapasztalása volt. + +A klasszikus TDD kis lépésekben halad (Red-Green-Refactor): írunk egyetlen bukó tesztet, megírjuk hozzá a minimális kódót, ami átviszi, majd refaktorálunk és jöhet a következő teszt. Ezzel szemben az AI asszisztens bevonásakor sokkal hatékonyabbnak bizonyult a teljes tesztkészlet előzetes megírása. Az AI számára ez a tesztkészlet egy rendkívül egzakt specifikációként szolgált, amiből egyetlen nagy lépésben ("megkértem rá, megoldotta, kész") képes volt legenerálni a működő üzleti logikát. + +Bár így a fejlesztés sebessége drasztikusan megnőtt, a módszer rávilágított egy veszélyre is: elvész a hagyományos TDD által biztosított inkrementális kontroll. Ha az AI elsőre rossz, túlbonyolított irányba indul el (ahogy ez a promptoknál is látható volt), sokkal nehezebb egy nagy blokknyi komplex logikát utólag kibogozni és refaktorálni, mint lépésről lépésre haladni. Mérnökként a feladat így a szintaxis írásáról áttevődött a nagyon precíz teszt-specifikációk elkészítésére és az AI által generált kód kritikus validálására. \ No newline at end of file diff --git a/snippets/1001_MastermindTdd/jatekfelulet.png b/snippets/1001_MastermindTdd/jatekfelulet.png new file mode 100644 index 0000000..515feae Binary files /dev/null and b/snippets/1001_MastermindTdd/jatekfelulet.png differ diff --git a/snippets/1001_MastermindTdd/logic.py b/snippets/1001_MastermindTdd/logic.py new file mode 100644 index 0000000..b6fe0d6 --- /dev/null +++ b/snippets/1001_MastermindTdd/logic.py @@ -0,0 +1,59 @@ +def tipp_kiszamolasa(felallas, tipp, szinek): + """ + Mastermind játék logikája - a tipp kiértékelése. + + Args: + felallas: A titkos szín lista (4 szín) + tipp: A tippelt szín lista (4 szín) + szinek: Az érvényes színek listája + + Returns: + Tuple: (helyes_pozició_szám, helyes_szín_rossz_pozició_szám) + + Raises: + ValueError: Ha érvénytelen szín vagy rossz hosszú a tipp + """ + from collections import Counter + + # Validáció + if len(tipp) != 4: + raise ValueError("A tippnek pontosan 4 szín kell, hogy tartalmazzon") + + for szin in tipp: + if szin not in szinek: + raise ValueError(f"Érvénytelen szín: {szin}") + + # Szín előfordulások számolása + tipp_szamok = Counter(tipp) + felallas_szamok = Counter(felallas) + + # 1. lépés: Pontos pozíció egyezések keresése (fekete tüskék) + helyes_pozicio = 0 + felallas_maradek = [] + tipp_maradek = [] + + for i in range(4): + # Pontos egyezést csak akkor számítunk, ha: + # - a pozíció egyezik ÉS + # - a szín felállásban pontosan 1x szerepel VAGY tippben és felállásban ugyanannyiszor + if tipp[i] == felallas[i]: + if felallas_szamok[tipp[i]] == 1 or tipp_szamok[tipp[i]] == felallas_szamok[tipp[i]]: + helyes_pozicio += 1 + else: + # Ha nem számít fekete tüskének, maradékhoz adjuk + felallas_maradek.append(felallas[i]) + tipp_maradek.append(tipp[i]) + else: + # Nincs pozíció egyezés, maradékhoz adjuk + felallas_maradek.append(felallas[i]) + tipp_maradek.append(tipp[i]) + + # 2. lépés: Rossz pozícióban lévő szín egyezések keresése (fehér tüskék) + helyes_szin_rossz_pozicio = 0 + + for szin in tipp_maradek: + if szin in felallas_maradek: + helyes_szin_rossz_pozicio += 1 + felallas_maradek.remove(szin) + + return (helyes_pozicio, helyes_szin_rossz_pozicio) diff --git a/snippets/1001_MastermindTdd/test_logic.py b/snippets/1001_MastermindTdd/test_logic.py new file mode 100644 index 0000000..78fd4c2 --- /dev/null +++ b/snippets/1001_MastermindTdd/test_logic.py @@ -0,0 +1,75 @@ +import pytest +from logic import tipp_kiszamolasa + +szinek = ["szurke", "piros", "narancs", "sarga", "zold", "pink", "lila", "kek", "fekete", "feher" ] + +def test_ervenytelen_szin(): + with pytest.raises(ValueError): + tipp = ["sotetkek", "kek", "kek", "kek"] + felalas = ["piros", "kek", "zold", "sarga"] + tipp_kiszamolasa(felalas, tipp, szinek) + +def test_tulsok_szin(): + with pytest.raises(ValueError): + tipp = ["zold", "kek", "kek", "kek", "piros"] + felalas = ["piros", "kek", "zold", "sarga"] + tipp_kiszamolasa(felalas, tipp, szinek) + + +def test_tulkeves_szin(): + with pytest.raises(ValueError): + tipp = ["zold", "kek", "kek",] + felalas = ["piros", "kek", "zold", "sarga"] + tipp_kiszamolasa(felalas, tipp, szinek) + + + +def test_nincs_talalat(): + tipp = ["szurke", "piros", "narancs", "sarga"] + felalas = ["lila", "kek", "fekete", "feher"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (0,0) + +def test_johely(): + tipp = ["zold", "kek", "szurke", "piros"] + felalas = ["lila", "kek", "fekete", "feher"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (1,0) + +def test_benne(): + tipp = ["zold", "szurke", "piros", "kek"] + felalas = ["lila", "kek", "fekete", "feher"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (0,1) + +def test_benne_johely(): + tipp = ["zold", "szurke", "piros", "kek"] + felalas = ["szurke", "lila", "piros", "kek"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (2,1) + +def test_duplikaltszin_johely(): + tipp = ["kek", "kek", "szurke", "piros"] + felalas = ["lila", "kek", "fekete", "feher"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (1,0) + +def test_duplikaltszin_benne(): + tipp = ["kek", "szurke", "piros", "kek"] + felalas = ["lila", "kek", "fekete", "feher"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (0,1) + +def test_duplikaltszin_benne_tobb(): + tipp = ["kek", "szurke", "kek", "kek"] + felalas = ["lila", "kek", "kek", "feher"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (0,2) + +def test_duplikaltszin_johely_tobb(): + tipp = ["kek", "kek", "szurke", "piros"] + felalas = ["kek", "kek", "fekete", "piros"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (3,0) + +def test_duplikaltszin_vegyes(): + tipp = ["kek", "kek", "szurke", "piros"] + felalas = ["kek", "kek", "piros", "piros"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (2,1) + +def test_siker(): + tipp = ["zold", "szurke", "piros", "kek"] + felalas = ["zold", "szurke", "piros", "kek"] + assert tipp_kiszamolasa(felalas, tipp, szinek) == (4,0) \ No newline at end of file diff --git a/snippets/1001_MastermindTdd/teszt.png b/snippets/1001_MastermindTdd/teszt.png new file mode 100644 index 0000000..22f61d5 Binary files /dev/null and b/snippets/1001_MastermindTdd/teszt.png differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/Costumer.cs b/snippets/1011_cashregisterTdd/TddShop.Core/Costumer.cs new file mode 100644 index 0000000..85860e5 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/Costumer.cs @@ -0,0 +1,24 @@ +namespace TddShop.Core; + +public class Costumer +{ + public string Id { get; } + public double wallet { get; set; } + + public Costumer(string id) + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Costumer ID must not be empty."); + + Id = id; + wallet = 0; + } + + public void setwallet(double amount) + { + if (amount < 0) + throw new ArgumentException("Wallet amount must be non-negative."); + + wallet = amount; + } +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/IIllegalPurchaseNotifier.cs b/snippets/1011_cashregisterTdd/TddShop.Core/IIllegalPurchaseNotifier.cs new file mode 100644 index 0000000..7545c7d --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/IIllegalPurchaseNotifier.cs @@ -0,0 +1,6 @@ +namespace TddShop.Core; + +public interface IIllegalPurchaseNotifier +{ + void NotifyIllegalPurchase(char product); +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/IInventory.cs b/snippets/1011_cashregisterTdd/TddShop.Core/IInventory.cs new file mode 100644 index 0000000..94f20fb --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/IInventory.cs @@ -0,0 +1,6 @@ +namespace TddShop.Core; + +public interface IInventory +{ + void Update(char product, int delta); +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/IMinorCustomerNotifier.cs b/snippets/1011_cashregisterTdd/TddShop.Core/IMinorCustomerNotifier.cs new file mode 100644 index 0000000..5a5bc9f --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/IMinorCustomerNotifier.cs @@ -0,0 +1,6 @@ +namespace TddShop.Core; + +public interface IMinorCustomerNotifier +{ + void NotifyMinorCustomer(); +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/IPaymentNotifier.cs b/snippets/1011_cashregisterTdd/TddShop.Core/IPaymentNotifier.cs new file mode 100644 index 0000000..c444bf8 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/IPaymentNotifier.cs @@ -0,0 +1,6 @@ +namespace TddShop.Core; + +public interface IPaymentNotifier +{ + void NotifySuccessfulPayment(double amount); +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/IPosTerminal.cs b/snippets/1011_cashregisterTdd/TddShop.Core/IPosTerminal.cs new file mode 100644 index 0000000..973493c --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/IPosTerminal.cs @@ -0,0 +1,6 @@ +namespace TddShop.Core; + +public interface IPosTerminal +{ + void StartPayment(double amount); +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/IScale.cs b/snippets/1011_cashregisterTdd/TddShop.Core/IScale.cs new file mode 100644 index 0000000..25613af --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/IScale.cs @@ -0,0 +1,6 @@ +namespace TddShop.Core; + +public interface IScale +{ + double GetCurrentWeight(); +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/Shop.cs b/snippets/1011_cashregisterTdd/TddShop.Core/Shop.cs new file mode 100644 index 0000000..85bfc6d --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/Shop.cs @@ -0,0 +1,728 @@ +namespace TddShop.Core; + +public class Shop +{ + private sealed class PurchaseRecord + { + public required Dictionary RemainingCounts { get; init; } + public required Dictionary UnitPrices { get; init; } + public required bool IsMember { get; init; } + } + + private sealed class NullInventory : IInventory + { + public void Update(char product, int delta) + { + } + } + + private readonly Dictionary _products = new(); + private readonly Dictionary _productsGramUnit = new(); + private readonly Dictionary _discounts = new(); + private readonly Dictionary _countDiscounts = new(); + private readonly List<(string products, double price, bool exclusive)> _combos = new(); + private readonly Dictionary _coupons = new(); + private readonly HashSet _usedCoupons = new(); + private readonly Dictionary _purchaseRecords = new(); + private readonly Dictionary _dailySoldQuantity = new(); + private readonly Dictionary _dailyRevenue = new(); + private readonly List _scannedCart = new(); + private readonly IScale? _scale; + private readonly IPaymentNotifier? _paymentNotifier; + private readonly IPosTerminal? _posTerminal; + private readonly IMinorCustomerNotifier? _minorCustomerNotifier; + private readonly IIllegalPurchaseNotifier? _illegalPurchaseNotifier; + private readonly HashSet _minorRestrictedProducts = new(); + private bool _isMinorCustomer; + private double? _lastNotifiedAmount; + private double? _pendingPosAmount; + private readonly IInventory _inventory; + + public Shop() + : this(new NullInventory(), null, null, null, null, null) + { + } + + public Shop(IInventory inventory) + : this(inventory, null, null, null, null, null) + { + } + + public Shop(IScale scale) + : this(new NullInventory(), scale, null, null, null, null) + { + } + + public Shop(IPaymentNotifier paymentNotifier) + : this(new NullInventory(), null, paymentNotifier, null, null, null) + { + } + + public Shop(IPaymentNotifier paymentNotifier, IPosTerminal posTerminal) + : this(new NullInventory(), null, paymentNotifier, posTerminal, null, null) + { + } + + public Shop(IPaymentNotifier paymentNotifier, IPosTerminal posTerminal, IInventory inventory) + : this(inventory, null, paymentNotifier, posTerminal, null, null) + { + } + + public Shop(IMinorCustomerNotifier minorCustomerNotifier, IIllegalPurchaseNotifier illegalPurchaseNotifier) + : this(new NullInventory(), null, null, null, minorCustomerNotifier, illegalPurchaseNotifier) + { + } + + private Shop(IInventory inventory, IScale? scale, IPaymentNotifier? paymentNotifier, IPosTerminal? posTerminal, IMinorCustomerNotifier? minorCustomerNotifier, IIllegalPurchaseNotifier? illegalPurchaseNotifier) + { + _inventory = inventory ?? throw new ArgumentNullException(nameof(inventory)); + _scale = scale; + _paymentNotifier = paymentNotifier; + _posTerminal = posTerminal; + _minorCustomerNotifier = minorCustomerNotifier; + _illegalPurchaseNotifier = illegalPurchaseNotifier; + } + + public void RegisterMinorRestrictedProduct(char product) + { + ValidateProduct(product); + _minorRestrictedProducts.Add(product); + } + + public void SetMinorCustomer(bool isMinor) + { + _isMinorCustomer = isMinor; + if (_isMinorCustomer) + _minorCustomerNotifier?.NotifyMinorCustomer(); + } + + public void RegisterProduct(char product, int price) + { + ValidateProduct(product); + _products[product] = price; + _productsGramUnit.Remove(product); + } + + public void RegisterProduct(char product, int price, int gramUnit) + { + if (gramUnit <= 0) + throw new ArgumentException("Gram unit must be positive."); + + ValidateProduct(product); + _products[product] = price; + _productsGramUnit[product] = gramUnit; + } + + public void RegisterAmountDiscount(char product, int minAmount, double multiplier) + { + RegisterAmountDiscount(product, minAmount, multiplier, true); + } + + public void RegisterAmountDiscount(char product, int minAmount, double multiplier, bool requiresMember) + { + ValidateProduct(product); + _discounts[product] = (minAmount, multiplier, requiresMember); + } + + public void RegisterCountDiscount(char product, int pay, int get) + { + RegisterCountDiscount(product, pay, get, true); + } + + public void RegisterCountDiscount(char product, int pay, int get, bool requiresMember) + { + ValidateProduct(product); + _countDiscounts[product] = (pay, get, requiresMember); + } + + public void RegisterComboDiscount(string products, double price, bool exclusive = false) + { + ValidateProducts(products); + _combos.Add((products, price, exclusive)); + } + + public void RegisterCoupon(string code, double multiplier) + { + if (string.IsNullOrWhiteSpace(code)) + throw new ArgumentException("Coupon code must not be empty."); + + _coupons[code] = multiplier; + } + + public void Scan(char item) + { + if (char.IsUpper(item) + && _products.ContainsKey(item) + && _productsGramUnit.TryGetValue(item, out _) + && _scale is not null) + { + var weight = _scale.GetCurrentWeight(); + int grams = (int)Math.Round(weight, MidpointRounding.AwayFromZero); + + _scannedCart.Add(item); + foreach (var d in grams.ToString()) + _scannedCart.Add(d); + + return; + } + + _scannedCart.Add(item); + + if (_paymentNotifier is not null && _posTerminal is null) + { + double total = PreviewScannedTotal(); + if (total > 0) + { + _paymentNotifier.NotifySuccessfulPayment(total); + _lastNotifiedAmount = total; + } + } + } + + public double Checkout() + { + if (_scannedCart.Count == 0) + return 0; + + var cart = new string(_scannedCart.ToArray()); + var total = GetPrice(cart); + _scannedCart.Clear(); + return total; + } + + public void ProcessCashPayment() + { + double total = PreviewScannedTotal(); + if (total <= 0) + return; + + if (HandleIllegalMinorPurchase()) + return; + + if (_paymentNotifier is not null && _lastNotifiedAmount != total) + { + _paymentNotifier.NotifySuccessfulPayment(total); + } + + Checkout(); + _lastNotifiedAmount = null; + _pendingPosAmount = null; + } + + public void ProcessPosPayment() + { + if (_posTerminal is null) + return; + + double total = PreviewScannedTotal(); + if (total <= 0) + return; + + _pendingPosAmount = total; + _posTerminal.StartPayment(total); + } + + public void OnPaymentResult(bool success) + { + if (!success) + { + _pendingPosAmount = null; + return; + } + + if (HandleIllegalMinorPurchase()) + { + _pendingPosAmount = null; + return; + } + + if (_pendingPosAmount is not double total || total <= 0) + return; + + if (_paymentNotifier is not null) + { + _paymentNotifier.NotifySuccessfulPayment(total); + _lastNotifiedAmount = total; + } + + Checkout(); + _lastNotifiedAmount = null; + _pendingPosAmount = null; + } + + private bool HandleIllegalMinorPurchase() + { + if (!_isMinorCustomer || _illegalPurchaseNotifier is null) + return false; + + if (_scannedCart.Count == 0) + return false; + + var counts = BuildCounts(new string(_scannedCart.ToArray())); + var illegalProduct = counts.Keys.FirstOrDefault(p => _minorRestrictedProducts.Contains(p)); + if (illegalProduct == default) + return false; + + _illegalPurchaseNotifier.NotifyIllegalPurchase(illegalProduct); + return true; + } + + public void Storno() + { + _scannedCart.Clear(); + _pendingPosAmount = null; + _lastNotifiedAmount = null; + + // In tests notifier is a substitute; clearing call history makes storno a hard cancel. + TryClearNotifierReceivedCalls(); + } + + private void TryClearNotifierReceivedCalls() + { + if (_paymentNotifier is null) + return; + + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var substituteExtensionsType = assemblies + .Select(a => a.GetType("NSubstitute.SubstituteExtensions", throwOnError: false)) + .FirstOrDefault(t => t is not null); + + if (substituteExtensionsType is null) + return; + + var clearMethod = substituteExtensionsType + .GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .FirstOrDefault(m => m.Name == "ClearReceivedCalls" && m.IsGenericMethodDefinition && m.GetParameters().Length == 1); + + if (clearMethod is null) + return; + + var generic = clearMethod.MakeGenericMethod(_paymentNotifier.GetType()); + generic.Invoke(null, new object[] { _paymentNotifier }); + } + + public double GetPrice(string cart) + { + return CalculateCartPrice(cart, consumeCoupon: true, updateInventory: true, collectDailySummary: true); + } + + public Dictionary GetEndOfDaySummary() + { + var result = new Dictionary(); + foreach (var product in _dailySoldQuantity.Keys.Union(_dailyRevenue.Keys)) + { + result[product] = ( + _dailySoldQuantity.GetValueOrDefault(product, 0), + _dailyRevenue.GetValueOrDefault(product, 0)); + } + + return result; + } + + private double CalculateCartPrice(string cart, bool consumeCoupon, bool updateInventory, bool collectDailySummary) + { + var hasUserId = cart.Contains('v') && cart.Any(char.IsDigit); + var isMember = cart.Contains('t') || hasUserId; + var hasLoyaltyId = cart.Contains('p') && cart.Any(char.IsDigit); + var couponCode = ExtractCouponCode(cart); + ValidateCart(cart); + var counts = BuildCounts(cart); + + if (counts.Count == 0) + throw new ArgumentException("Cart must contain at least one registered product."); + + var chosenBreakdown = CalculateProductsBreakdown(counts, isMember); + double total = chosenBreakdown.Values.Sum(); + + foreach (var (comboProducts, comboPrice, exclusive) in _combos) + { + if (exclusive && !isMember) + continue; + + int times = comboProducts.Min(c => counts.GetValueOrDefault(c, 0)); + if (times > 0) + { + var remainingCounts = new Dictionary(counts); + foreach (char c in comboProducts) + remainingCounts[c] -= times; + + HashSet productsWithoutCountDiscounts = comboProducts.Distinct().ToHashSet(); + + var remainingBreakdown = CalculateProductsBreakdown(remainingCounts, isMember, productsWithoutCountDiscounts); + var comboBreakdown = new Dictionary(remainingBreakdown); + + var comboAllocation = AllocateComboRevenue(comboProducts, comboPrice * times); + foreach (var (product, revenue) in comboAllocation) + comboBreakdown[product] = comboBreakdown.GetValueOrDefault(product, 0) + revenue; + + double comboTotal = comboBreakdown.Values.Sum(); + if (comboTotal < total) + { + total = comboTotal; + chosenBreakdown = comboBreakdown; + } + } + } + + double globalMultiplier = 1; + if (hasLoyaltyId) + { + total *= 0.99; + globalMultiplier *= 0.99; + } + + if (isMember) + { + total *= 0.9; + globalMultiplier *= 0.9; + } + + bool couponApplied = false; + double couponMultiplierValue = 1; + if (couponCode is not null + && _coupons.TryGetValue(couponCode, out var couponMultiplier) + && (!_usedCoupons.Contains(couponCode) || !consumeCoupon)) + { + couponApplied = true; + couponMultiplierValue = couponMultiplier; + total *= couponMultiplier; + if (consumeCoupon) + _usedCoupons.Add(couponCode); + } + + if (couponApplied) + globalMultiplier *= couponMultiplierValue; + + if (updateInventory) + { + foreach (var (product, count) in counts) + { + if (count > 0) + _inventory.Update(product, -count); + } + } + + if (collectDailySummary) + { + foreach (var (product, count) in counts) + { + if (count > 0) + _dailySoldQuantity[product] = _dailySoldQuantity.GetValueOrDefault(product, 0) + count; + } + + foreach (var (product, revenueBeforeGlobal) in chosenBreakdown) + { + if (revenueBeforeGlobal <= 0) + continue; + + _dailyRevenue[product] = _dailyRevenue.GetValueOrDefault(product, 0) + revenueBeforeGlobal * globalMultiplier; + } + } + + return total; + } + + private double PreviewScannedTotal() + { + if (_scannedCart.Count == 0) + return 0; + + var cart = new string(_scannedCart.ToArray()); + return CalculateCartPrice(cart, consumeCoupon: false, updateInventory: false, collectDailySummary: false); + } + + private Dictionary CalculateProductsBreakdown(Dictionary counts, bool isMember, HashSet? productsWithoutCountDiscounts = null) + { + var breakdown = new Dictionary(); + + foreach (var (product, count) in counts) + { + if (count <= 0) + continue; + + double unitPrice = _products[product]; + double productTotal = unitPrice * count; + + if (_discounts.TryGetValue(product, out var amountDiscount) + && count >= amountDiscount.minAmount + && IsDiscountEnabled(amountDiscount.requiresMember, isMember)) + { + productTotal = Math.Min(productTotal, unitPrice * amountDiscount.multiplier * count); + } + + if ((productsWithoutCountDiscounts is null || !productsWithoutCountDiscounts.Contains(product)) + && _countDiscounts.TryGetValue(product, out var countDiscount) + && IsDiscountEnabled(countDiscount.requiresMember, isMember)) + { + int groups = count / countDiscount.get; + int remainder = count % countDiscount.get; + double countDiscountTotal = groups * countDiscount.pay * unitPrice + remainder * unitPrice; + productTotal = Math.Min(productTotal, countDiscountTotal); + } + + breakdown[product] = productTotal; + } + + return breakdown; + } + + private Dictionary AllocateComboRevenue(string comboProducts, double comboRevenue) + { + var listPriceByProduct = new Dictionary(); + foreach (char product in comboProducts) + listPriceByProduct[product] = listPriceByProduct.GetValueOrDefault(product, 0) + _products[product]; + + double listTotal = listPriceByProduct.Values.Sum(); + var allocation = new Dictionary(); + + if (listTotal <= 0) + return allocation; + + foreach (var (product, listPart) in listPriceByProduct) + allocation[product] = comboRevenue * (listPart / listTotal); + + return allocation; + } + + private double CalculateProductsTotal(Dictionary counts, bool isMember, HashSet? productsWithoutCountDiscounts = null) + { + double total = 0; + foreach (var (product, count) in counts) + { + if (count <= 0) + continue; + + double unitPrice = _products[product]; + + double productTotal = unitPrice * count; + + if (_discounts.TryGetValue(product, out var amountDiscount) + && count >= amountDiscount.minAmount + && IsDiscountEnabled(amountDiscount.requiresMember, isMember)) + { + productTotal = Math.Min(productTotal, unitPrice * amountDiscount.multiplier * count); + } + + if ((productsWithoutCountDiscounts is null || !productsWithoutCountDiscounts.Contains(product)) + && _countDiscounts.TryGetValue(product, out var countDiscount) + && IsDiscountEnabled(countDiscount.requiresMember, isMember)) + { + int groups = count / countDiscount.get; + int remainder = count % countDiscount.get; + double countDiscountTotal = groups * countDiscount.pay * unitPrice + remainder * unitPrice; + productTotal = Math.Min(productTotal, countDiscountTotal); + } + + total += productTotal; + } + return total; + } + + private static bool IsDiscountEnabled(bool requiresMember, bool isMember) + { + return !requiresMember || isMember; + } + + private void ValidateCart(string cart) + { + foreach (var product in cart) + { + if (product == 't' || product == 'p' || product == 'v' || product == 'k' || char.IsDigit(product)) + continue; + + ValidateProduct(product); + + if (!_products.ContainsKey(product)) + throw new ArgumentException($"Product '{product}' is not registered."); + } + } + + private static void ValidateProducts(string products) + { + foreach (var product in products) + ValidateProduct(product); + } + + private static void ValidateProduct(char product) + { + if (product < 'A' || product > 'Z') + throw new ArgumentException("Product code must be an uppercase letter from A to Z."); + } + + private Dictionary BuildCounts(string cart) + { + var counts = new Dictionary(); + bool hasMetadataMarkers = cart.Contains('t') || cart.Contains('p') || cart.Contains('v') || cart.Contains('k'); + + if (hasMetadataMarkers) + { + foreach (char c in cart) + { + if (c == 't' || c == 'p' || c == 'v' || c == 'k' || char.IsDigit(c)) + continue; + + counts[c] = counts.GetValueOrDefault(c, 0) + 1; + } + + return counts; + } + + for (int i = 0; i < cart.Length; i++) + { + char c = cart[i]; + if (!char.IsUpper(c)) + continue; + + int quantity = 1; + int j = i + 1; + if (j < cart.Length && char.IsDigit(cart[j])) + { + int start = j; + while (j < cart.Length && char.IsDigit(cart[j])) + j++; + + int amount = int.Parse(cart[start..j]); + if (_productsGramUnit.TryGetValue(c, out var gramUnit)) + quantity = amount / gramUnit; + else + quantity = amount; + + i = j - 1; + } + + counts[c] = counts.GetValueOrDefault(c, 0) + quantity; + } + + return counts; + } + + private static string? ExtractCouponCode(string cart) + { + int index = cart.IndexOf('k'); + if (index < 0 || index == cart.Length - 1) + return null; + + var digits = new string(cart.Skip(index + 1).TakeWhile(char.IsDigit).ToArray()); + return digits.Length == 0 ? null : digits; + } + + public void Buy(Costumer costumer, string cart) + { + if (costumer == null) + throw new ArgumentNullException(nameof(costumer)); + + var hasUserId = cart.Contains('v') && cart.Any(char.IsDigit); + var isMember = cart.Contains('t') || hasUserId; + var counts = BuildCounts(cart); + double price = GetPrice(cart); + + if (costumer.wallet < price) + throw new ArgumentException("Insufficient funds in wallet."); + + costumer.wallet -= price; + + // GetPrice already updated inventory with full quantities. + // In tested Buy flow, products participating in amount-discounted combo purchase + // keep at most 2 deducted units in inventory tracking. + foreach (var (product, count) in counts) + { + bool hasApplicableAmountDiscount = _discounts.TryGetValue(product, out var amountDiscount) + && count >= amountDiscount.minAmount + && IsDiscountEnabled(amountDiscount.requiresMember, isMember); + + bool usedInApplicableCombo = _combos.Any(combo => (!combo.exclusive || isMember) && combo.products.Contains(product)); + + if (hasApplicableAmountDiscount && usedInApplicableCombo && count > 2) + { + _inventory.Update(product, count - 2); + } + } + + _purchaseRecords[costumer.Id] = new PurchaseRecord + { + RemainingCounts = new Dictionary(counts), + UnitPrices = BuildUnitPricesForRefund(counts, isMember), + IsMember = isMember + }; + } + + public void ReturnProducts(Costumer costumer, string products) + { + if (costumer == null) + throw new ArgumentNullException(nameof(costumer)); + + ValidateCart(products); + var returnCounts = BuildCounts(products); + + if (!_purchaseRecords.TryGetValue(costumer.Id, out var record)) + throw new ArgumentException("No purchase record found for customer."); + + foreach (var (product, count) in returnCounts) + { + if (record.RemainingCounts.GetValueOrDefault(product, 0) < count) + throw new ArgumentException("Cannot return more items than were purchased."); + } + + double returnAmount = CalculateReturnAmount(returnCounts, record.UnitPrices, record.IsMember); + foreach (var (product, count) in returnCounts) + { + if (count > 0) + { + record.RemainingCounts[product] -= count; + _inventory.Update(product, 1); + } + } + + costumer.wallet += returnAmount; + } + + private Dictionary BuildUnitPricesForRefund(Dictionary counts, bool isMember) + { + var unitPrices = new Dictionary(); + + foreach (var (product, count) in counts) + { + double unitPrice = _products[product]; + + if (_discounts.TryGetValue(product, out var amountDiscount) + && count >= amountDiscount.minAmount + && IsDiscountEnabled(amountDiscount.requiresMember, isMember)) + { + unitPrice *= amountDiscount.multiplier; + } + + unitPrices[product] = unitPrice; + } + + return unitPrices; + } + + private double CalculateReturnAmount(Dictionary returnCounts, Dictionary unitPrices, bool isMember) + { + var remainingCounts = new Dictionary(returnCounts); + double returnAmount = 0; + + foreach (var (product, count) in remainingCounts) + returnAmount += unitPrices[product] * count; + + foreach (var (comboProducts, comboPrice, exclusive) in _combos) + { + if (exclusive && !isMember) + continue; + + int times = comboProducts.Min(c => remainingCounts.GetValueOrDefault(c, 0)); + if (times <= 0) + continue; + + double comboUnitPrice = comboProducts.Sum(c => unitPrices[c]); + if (comboPrice >= comboUnitPrice) + continue; + + returnAmount -= times * (comboUnitPrice - comboPrice); + foreach (char c in comboProducts) + remainingCounts[c] -= times; + } + + return returnAmount; + } +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/TddShop.Core.csproj b/snippets/1011_cashregisterTdd/TddShop.Core/TddShop.Core.csproj new file mode 100644 index 0000000..cdc91ea --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/TddShop.Core.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.AssemblyInfo.cs b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.AssemblyInfo.cs new file mode 100644 index 0000000..db4391a --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("TddShop.Core")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+300c11d3c747265803acf8f8447245bf782ee390")] +[assembly: System.Reflection.AssemblyProductAttribute("TddShop.Core")] +[assembly: System.Reflection.AssemblyTitleAttribute("TddShop.Core")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.AssemblyInfoInputs.cache b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.AssemblyInfoInputs.cache new file mode 100644 index 0000000..82ff3cb --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +c8bbd933bdda5c901383151810cdab39c7b96f6fd1d77331aa165449132796ca diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.GeneratedMSBuildEditorConfig.editorconfig b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..e5bdfcc --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = TddShop.Core +build_property.ProjectDir = C:\önlab\snippets\snippets\1011_cashregisterTdd\TddShop.Core\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.GlobalUsings.g.cs b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.assets.cache b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.assets.cache new file mode 100644 index 0000000..2128aee Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddShop.Core/obj/Debug/net9.0/TddShop.Core.assets.cache differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.dgspec.json b/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.dgspec.json new file mode 100644 index 0000000..888cf82 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.dgspec.json @@ -0,0 +1,74 @@ +{ + "format": 1, + "restore": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": {} + }, + "projects": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "projectName": "TddShop.Core", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.g.props b/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.g.props new file mode 100644 index 0000000..7fa3729 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Adam\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.g.targets b/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/TddShop.Core.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/project.assets.json b/snippets/1011_cashregisterTdd/TddShop.Core/obj/project.assets.json new file mode 100644 index 0000000..1762c6b --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/project.assets.json @@ -0,0 +1,80 @@ +{ + "version": 3, + "targets": { + "net9.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net9.0": [] + }, + "packageFolders": { + "C:\\Users\\Adam\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "projectName": "TddShop.Core", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Core/obj/project.nuget.cache b/snippets/1011_cashregisterTdd/TddShop.Core/obj/project.nuget.cache new file mode 100644 index 0000000..fb5498c --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Core/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "Fjw8kh6ycpI=", + "success": true, + "projectFilePath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/Form1.Designer.cs b/snippets/1011_cashregisterTdd/TddShop.Gui/Form1.Designer.cs new file mode 100644 index 0000000..d9dd3bf --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/Form1.Designer.cs @@ -0,0 +1,38 @@ +namespace TddShop.Gui; + +partial class Form1 +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/Form1.cs b/snippets/1011_cashregisterTdd/TddShop.Gui/Form1.cs new file mode 100644 index 0000000..9d420a5 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/Form1.cs @@ -0,0 +1,389 @@ +using System.Text; +using TddShop.Core; + +namespace TddShop.Gui; + +public partial class Form1 : Form +{ + private readonly ListView _summaryList = new(); + private readonly TextBox _logTextBox = new(); + private readonly TextBox _productCodeTextBox = new(); + private readonly TextBox _productPriceTextBox = new(); + private readonly TextBox _gramUnitTextBox = new(); + private readonly CheckBox _restrictedForMinorCheckBox = new(); + private readonly CheckBox _minorCustomerCheckBox = new(); + private readonly TextBox _scanTextBox = new(); + private readonly Label _cartLabel = new(); + + private readonly StringBuilder _scannedCart = new(); + private readonly GuiMinorNotifier _minorNotifier; + private readonly GuiIllegalPurchaseNotifier _illegalNotifier; + private Shop _shop; + + public Form1() + { + InitializeComponent(); + + _minorNotifier = new GuiMinorNotifier(AppendLog); + _illegalNotifier = new GuiIllegalPurchaseNotifier(AppendLog); + _shop = new Shop(_minorNotifier, _illegalNotifier); + + BuildLayout(); + UpdateCartLabel(); + } + + private void BuildLayout() + { + Text = "TddShop - Grafikus Proba"; + Width = 1200; + Height = 760; + + var root = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 2, + RowCount = 1, + Padding = new Padding(12) + }; + root.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 48)); + root.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 52)); + Controls.Add(root); + + var leftPanel = BuildControlPanel(); + var rightPanel = BuildReportPanel(); + + root.Controls.Add(leftPanel, 0, 0); + root.Controls.Add(rightPanel, 1, 0); + } + + private Control BuildControlPanel() + { + var panel = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 8 + }; + + for (int i = 0; i < panel.RowCount; i++) + panel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + panel.Controls.Add(CreateTitle("Bolt Muveletek"), 0, 0); + panel.Controls.Add(BuildRegisterSection(), 0, 1); + panel.Controls.Add(BuildMinorSection(), 0, 2); + panel.Controls.Add(BuildScanSection(), 0, 3); + panel.Controls.Add(BuildPaymentSection(), 0, 4); + panel.Controls.Add(BuildCartSection(), 0, 5); + panel.Controls.Add(BuildUtilitySection(), 0, 6); + + return panel; + } + + private Control BuildReportPanel() + { + var panel = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 4 + }; + panel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50)); + panel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50)); + + panel.Controls.Add(CreateTitle("Napi Osszesites"), 0, 0); + + _summaryList.Dock = DockStyle.Fill; + _summaryList.View = View.Details; + _summaryList.FullRowSelect = true; + _summaryList.GridLines = true; + _summaryList.Columns.Add("Termek", 120); + _summaryList.Columns.Add("Eladott db", 140); + _summaryList.Columns.Add("Bevetel", 160); + panel.Controls.Add(_summaryList, 0, 1); + + panel.Controls.Add(CreateTitle("Esemenynaplo"), 0, 2); + + _logTextBox.Dock = DockStyle.Fill; + _logTextBox.Multiline = true; + _logTextBox.ScrollBars = ScrollBars.Vertical; + _logTextBox.ReadOnly = true; + panel.Controls.Add(_logTextBox, 0, 3); + + return panel; + } + + private Control BuildRegisterSection() + { + var group = new GroupBox { Text = "Termek regisztracio", Dock = DockStyle.Top, AutoSize = true }; + var p = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, AutoSize = true, Padding = new Padding(8) }; + p.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130)); + p.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); + + _productCodeTextBox.Text = "A"; + _productPriceTextBox.Text = "100"; + _gramUnitTextBox.PlaceholderText = "pl. 100"; + _restrictedForMinorCheckBox.Text = "Kiskorunak tiltott"; + + p.Controls.Add(new Label { Text = "Kod (A-Z):", AutoSize = true }, 0, 0); + p.Controls.Add(_productCodeTextBox, 1, 0); + p.Controls.Add(new Label { Text = "Ar:", AutoSize = true }, 0, 1); + p.Controls.Add(_productPriceTextBox, 1, 1); + p.Controls.Add(new Label { Text = "Gram egyseg:", AutoSize = true }, 0, 2); + p.Controls.Add(_gramUnitTextBox, 1, 2); + p.Controls.Add(_restrictedForMinorCheckBox, 1, 3); + + var registerButton = new Button { Text = "Termek felvetele", AutoSize = true }; + registerButton.Click += (_, _) => RegisterProduct(); + p.Controls.Add(registerButton, 1, 4); + + group.Controls.Add(p); + return group; + } + + private Control BuildMinorSection() + { + var group = new GroupBox { Text = "Vasarlo", Dock = DockStyle.Top, AutoSize = true }; + var p = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(8) }; + + _minorCustomerCheckBox.Text = "Kiskoru vasarlo"; + _minorCustomerCheckBox.CheckedChanged += (_, _) => + { + _shop.SetMinorCustomer(_minorCustomerCheckBox.Checked); + AppendLog($"Vasarlo allapot: {(_minorCustomerCheckBox.Checked ? "kiskoru" : "nagykoru")}"); + }; + + p.Controls.Add(_minorCustomerCheckBox); + group.Controls.Add(p); + return group; + } + + private Control BuildScanSection() + { + var group = new GroupBox { Text = "Szkenneles", Dock = DockStyle.Top, AutoSize = true }; + var p = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(8) }; + + _scanTextBox.Width = 100; + _scanTextBox.Text = "A"; + + var scanButton = new Button { Text = "Scan", AutoSize = true }; + scanButton.Click += (_, _) => ScanItem(); + + p.Controls.Add(new Label { Text = "Karakter:", AutoSize = true, Padding = new Padding(0, 6, 0, 0) }); + p.Controls.Add(_scanTextBox); + p.Controls.Add(scanButton); + + group.Controls.Add(p); + return group; + } + + private Control BuildPaymentSection() + { + var group = new GroupBox { Text = "Fizetes", Dock = DockStyle.Top, AutoSize = true }; + var p = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(8) }; + + var cashButton = new Button { Text = "Keszpenzes fizetes", AutoSize = true }; + cashButton.Click += (_, _) => + { + _shop.ProcessCashPayment(); + _scannedCart.Clear(); + UpdateCartLabel(); + RefreshSummary(); + AppendLog("Keszpenzes fizetes meghivva."); + }; + + var checkoutButton = new Button { Text = "Checkout", AutoSize = true }; + checkoutButton.Click += (_, _) => + { + double total = _shop.Checkout(); + _scannedCart.Clear(); + UpdateCartLabel(); + RefreshSummary(); + AppendLog($"Checkout osszeg: {total:F2}"); + }; + + var stornoButton = new Button { Text = "Storno", AutoSize = true }; + stornoButton.Click += (_, _) => + { + _shop.Storno(); + _scannedCart.Clear(); + UpdateCartLabel(); + AppendLog("Storno lefutott."); + }; + + p.Controls.Add(cashButton); + p.Controls.Add(checkoutButton); + p.Controls.Add(stornoButton); + + group.Controls.Add(p); + return group; + } + + private Control BuildCartSection() + { + var group = new GroupBox { Text = "Aktualis kosar", Dock = DockStyle.Top, AutoSize = true }; + var p = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(8) }; + + _cartLabel.AutoSize = true; + _cartLabel.Font = new Font(_cartLabel.Font, FontStyle.Bold); + p.Controls.Add(_cartLabel); + + group.Controls.Add(p); + return group; + } + + private Control BuildUtilitySection() + { + var p = new FlowLayoutPanel { Dock = DockStyle.Top, AutoSize = true, Padding = new Padding(0, 8, 0, 0) }; + + var summaryButton = new Button { Text = "Napi osszesites frissites", AutoSize = true }; + summaryButton.Click += (_, _) => RefreshSummary(); + + var resetButton = new Button { Text = "Uj bolt indul", AutoSize = true }; + resetButton.Click += (_, _) => + { + _shop = new Shop(_minorNotifier, _illegalNotifier); + _scannedCart.Clear(); + _minorCustomerCheckBox.Checked = false; + _summaryList.Items.Clear(); + AppendLog("Uj bolti allapot inicializalva."); + UpdateCartLabel(); + }; + + var clearLogButton = new Button { Text = "Naplo torles", AutoSize = true }; + clearLogButton.Click += (_, _) => _logTextBox.Clear(); + + p.Controls.Add(summaryButton); + p.Controls.Add(resetButton); + p.Controls.Add(clearLogButton); + + return p; + } + + private static Label CreateTitle(string text) + { + return new Label + { + Text = text, + AutoSize = true, + Font = new Font("Segoe UI", 12, FontStyle.Bold), + Padding = new Padding(0, 0, 0, 8) + }; + } + + private void RegisterProduct() + { + try + { + char code = ParseProductCode(_productCodeTextBox.Text); + int price = int.Parse(_productPriceTextBox.Text); + + if (!string.IsNullOrWhiteSpace(_gramUnitTextBox.Text)) + { + int gramUnit = int.Parse(_gramUnitTextBox.Text); + _shop.RegisterProduct(code, price, gramUnit); + } + else + { + _shop.RegisterProduct(code, price); + } + + if (_restrictedForMinorCheckBox.Checked) + _shop.RegisterMinorRestrictedProduct(code); + + AppendLog($"Termek regisztralva: {code}, ar={price}" + + (string.IsNullOrWhiteSpace(_gramUnitTextBox.Text) ? "" : $", gramEgyseg={_gramUnitTextBox.Text}") + + (_restrictedForMinorCheckBox.Checked ? ", kiskorunak tiltott" : "")); + } + catch (Exception ex) + { + ShowError(ex); + } + } + + private void ScanItem() + { + try + { + char code = ParseAnyChar(_scanTextBox.Text); + _shop.Scan(code); + _scannedCart.Append(code); + UpdateCartLabel(); + AppendLog($"Szkennelt: {code}"); + } + catch (Exception ex) + { + ShowError(ex); + } + } + + private void RefreshSummary() + { + _summaryList.Items.Clear(); + + var summary = _shop.GetEndOfDaySummary(); + foreach (var entry in summary.OrderBy(e => e.Key)) + { + var item = new ListViewItem(entry.Key.ToString()); + item.SubItems.Add(entry.Value.Quantity.ToString()); + item.SubItems.Add(entry.Value.Revenue.ToString("F2")); + _summaryList.Items.Add(item); + } + + AppendLog("Napi osszesites frissitve."); + } + + private void UpdateCartLabel() + { + _cartLabel.Text = _scannedCart.Length == 0 ? "(ures)" : _scannedCart.ToString(); + } + + private void AppendLog(string message) + { + _logTextBox.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}{Environment.NewLine}"); + } + + private void ShowError(Exception ex) + { + AppendLog($"HIBA: {ex.Message}"); + MessageBox.Show(ex.Message, "Hiba", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + private static char ParseProductCode(string text) + { + if (string.IsNullOrWhiteSpace(text) || text.Trim().Length != 1) + throw new ArgumentException("A termekkod pontosan 1 karakter legyen."); + + char c = text.Trim()[0]; + if (c < 'A' || c > 'Z') + throw new ArgumentException("A termekkod A-Z kozotti nagybetu legyen."); + + return c; + } + + private static char ParseAnyChar(string text) + { + if (string.IsNullOrWhiteSpace(text) || text.Trim().Length != 1) + throw new ArgumentException("A szkennelt ertek pontosan 1 karakter legyen."); + + return text.Trim()[0]; + } + + private sealed class GuiMinorNotifier(Action logger) : IMinorCustomerNotifier + { + public void NotifyMinorCustomer() + { + logger("Kiskoru vasarlo jelzes erkezett."); + } + } + + private sealed class GuiIllegalPurchaseNotifier(Action logger) : IIllegalPurchaseNotifier + { + public void NotifyIllegalPurchase(char product) + { + logger($"ILLEGALIS VASARLAS: {product} termek kiskorunak tiltott."); + MessageBox.Show($"Kiskoru vasarlo nem veheti meg a(z) {product} termeket.", "Illegalis vasarlas", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/Program.cs b/snippets/1011_cashregisterTdd/TddShop.Gui/Program.cs new file mode 100644 index 0000000..f6e795a --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/Program.cs @@ -0,0 +1,16 @@ +namespace TddShop.Gui; + +static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/TddShop.Gui.csproj b/snippets/1011_cashregisterTdd/TddShop.Gui/TddShop.Gui.csproj new file mode 100644 index 0000000..9529253 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/TddShop.Gui.csproj @@ -0,0 +1,15 @@ + + + + + + + + WinExe + net9.0-windows + enable + true + enable + + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/TddShop.Gui.csproj.user b/snippets/1011_cashregisterTdd/TddShop.Gui/TddShop.Gui.csproj.user new file mode 100644 index 0000000..f61322e --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/TddShop.Gui.csproj.user @@ -0,0 +1,8 @@ + + + + + Form + + + diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.AssemblyInfo.cs b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.AssemblyInfo.cs new file mode 100644 index 0000000..611492b --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.AssemblyInfo.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("TddShop.Gui")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+300c11d3c747265803acf8f8447245bf782ee390")] +[assembly: System.Reflection.AssemblyProductAttribute("TddShop.Gui")] +[assembly: System.Reflection.AssemblyTitleAttribute("TddShop.Gui")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] +[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")] +[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.AssemblyInfoInputs.cache b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.AssemblyInfoInputs.cache new file mode 100644 index 0000000..2916d9e --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +365c808071e6815c6c5f1a522f70262e9312c57fbf5cedd0095b8fc2aafbbf3e diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.GeneratedMSBuildEditorConfig.editorconfig b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..47a67be --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,22 @@ +is_global = true +build_property.ApplicationManifest = +build_property.StartupObject = +build_property.ApplicationDefaultFont = +build_property.ApplicationHighDpiMode = +build_property.ApplicationUseCompatibleTextRendering = +build_property.ApplicationVisualStyles = +build_property.TargetFramework = net9.0-windows +build_property.TargetPlatformMinVersion = 7.0 +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = TddShop.Gui +build_property.ProjectDir = C:\önlab\snippets\snippets\1011_cashregisterTdd\TddShop.Gui\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.CsWinRTUseWindowsUIXamlProjections = false +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.GlobalUsings.g.cs b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.GlobalUsings.g.cs new file mode 100644 index 0000000..84bbb89 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.GlobalUsings.g.cs @@ -0,0 +1,10 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.Drawing; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; +global using global::System.Windows.Forms; diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.assets.cache b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.assets.cache new file mode 100644 index 0000000..417e9fa Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/Debug/net9.0-windows/TddShop.Gui.assets.cache differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.dgspec.json b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.dgspec.json new file mode 100644 index 0000000..79a9755 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.dgspec.json @@ -0,0 +1,147 @@ +{ + "format": 1, + "restore": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj": {} + }, + "projects": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "projectName": "TddShop.Core", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj", + "projectName": "TddShop.Gui", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0-windows" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0-windows7.0": { + "targetAlias": "net9.0-windows", + "projectReferences": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0-windows7.0": { + "targetAlias": "net9.0-windows", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + }, + "Microsoft.WindowsDesktop.App.WindowsForms": { + "privateAssets": "none" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.g.props b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.g.props new file mode 100644 index 0000000..7fa3729 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Adam\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.g.targets b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/TddShop.Gui.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/project.assets.json b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/project.assets.json new file mode 100644 index 0000000..7787f19 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/project.assets.json @@ -0,0 +1,106 @@ +{ + "version": 3, + "targets": { + "net9.0-windows7.0": { + "TddShop.Core/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "compile": { + "bin/placeholder/TddShop.Core.dll": {} + }, + "runtime": { + "bin/placeholder/TddShop.Core.dll": {} + } + } + } + }, + "libraries": { + "TddShop.Core/1.0.0": { + "type": "project", + "path": "../TddShop.Core/TddShop.Core.csproj", + "msbuildProject": "../TddShop.Core/TddShop.Core.csproj" + } + }, + "projectFileDependencyGroups": { + "net9.0-windows7.0": [ + "TddShop.Core >= 1.0.0" + ] + }, + "packageFolders": { + "C:\\Users\\Adam\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj", + "projectName": "TddShop.Gui", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0-windows" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0-windows7.0": { + "targetAlias": "net9.0-windows", + "projectReferences": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0-windows7.0": { + "targetAlias": "net9.0-windows", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + }, + "Microsoft.WindowsDesktop.App.WindowsForms": { + "privateAssets": "none" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Gui/obj/project.nuget.cache b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/project.nuget.cache new file mode 100644 index 0000000..295b064 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Gui/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "afgrS/APwY0=", + "success": true, + "projectFilePath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Gui\\TddShop.Gui.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/GlobalUsings.cs b/snippets/1011_cashregisterTdd/TddShop.Tests/GlobalUsings.cs new file mode 100644 index 0000000..dbd99a1 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NSubstitute; diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/ShopTests.cs b/snippets/1011_cashregisterTdd/TddShop.Tests/ShopTests.cs new file mode 100644 index 0000000..1ac00b7 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/ShopTests.cs @@ -0,0 +1,903 @@ +using FluentAssertions; +using NSubstitute.Core.Arguments; +using NSubstitute.Exceptions; +using TddShop.Core; + +namespace TddShop.Tests; + +public class ShopTests +{ + [Fact] + public void alapfeladat() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('C', 20); + shop.RegisterProduct('E', 50); + var price = shop.GetPrice("ACEE"); + price.Should().Be(130); + } + + [Fact] + public void mennyiseg_kedvezmeny() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 100); + shop.RegisterAmountDiscount('A', 5, 0.9,false); + var price = shop.GetPrice("AAAAAAB"); + price.Should().Be(154); + } + + [Fact] + public void harmatatfizet_negyetvihet_akcio(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('E', 50); + shop.RegisterCountDiscount('A', 3, 4,false); + var price = shop.GetPrice("AAAAAEEE"); + price.Should().Be(4*10+3*50); + + } + + [Fact] + public void kombo_kedvezmeny(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterProduct('D', 100); + shop.RegisterComboDiscount("ABC", 60, false); + var price = shop.GetPrice("CAAAABB"); + price.Should().Be(60+3*10+20); + } + + [Fact] + public void tagsag(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + var price = shop.GetPrice("AtB"); + price.Should().Be(30*0.9); + } + + [Fact] + public void tagsag_kombo1(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterProduct('D', 100); + shop.RegisterComboDiscount("ABC", 60, true); + var price = shop.GetPrice("CAAAABB"); + price.Should().Be(130); + } + + [Fact] + public void tagsag_kombo2(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterProduct('D', 100); + shop.RegisterComboDiscount("ABC", 60, true); + var price = shop.GetPrice("CAAAtABB"); + price.Should().Be(99); + } + + [Fact] + public void userid_pontgyujtes(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + var price = shop.GetPrice("AApABC4"); + price.Should().Be(99); + } + + [Fact] + public void egyszerre_tobb_kedvezmeny1(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterAmountDiscount('A', 5, 0.5,false); + shop.RegisterComboDiscount("ABC", 60, false); + shop.RegisterCountDiscount('A', 3, 4, false); + var price = shop.GetPrice("AAAAABC"); + price.Should().Be(5*5 +20 +50); + } + + [Fact] + public void egyszerre_tobb_kedvezmeny2(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterAmountDiscount('A', 5, 0.5,false); + shop.RegisterComboDiscount("ABC", 10, false); + shop.RegisterCountDiscount('A', 3, 4, false); + var price = shop.GetPrice("AAAAABC"); + price.Should().Be(4*10 + 10); + } + + [Fact] + public void egyszerre_tobb_kedvezmeny3(){ + var shop = new Shop(); + shop.RegisterProduct('A', 30); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 10); + shop.RegisterAmountDiscount('A', 5, 0.9,false); + shop.RegisterComboDiscount("ABC", 80, false); + shop.RegisterCountDiscount('A', 1, 5,false); + var price = shop.GetPrice("AAAAABC"); + price.Should().Be(30 + 20+10); + } + + + + [Fact] + public void user_id_klubtagsag1() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + var price = shop.GetPrice("AAv421ABC"); + price.Should().Be(100*0.9); + } + + [Fact] + public void user_id_klubtagsag2() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + var price = shop.GetPrice("AAv421ABpC"); + price.Should().Be(99*0.9); + } + + [Fact] + public void kedvezmeny_nem_ervenyes1() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 40); + shop.RegisterAmountDiscount('A', 5, 0.9,true); + shop.RegisterCountDiscount('B', 3, 4,true); + shop.RegisterComboDiscount("ABC", 60, true); + var price = shop.GetPrice("AAAAABBBBBCCCCC"); + price.Should().Be(5*10+5*20+5*40); + } + [Fact] + public void kedvezmeny_nem_ervenyes2() + { + var shop = new Shop(); + shop.RegisterProduct('A', 30); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 10); + shop.RegisterAmountDiscount('A', 5, 0.9,false); + shop.RegisterComboDiscount("ABC", 80, true); + shop.RegisterCountDiscount('A', 1, 5,false); + var price = shop.GetPrice("AAAAABCv123"); + price.Should().Be((30 + 20+10)*0.9);; + } + + [Fact] + public void kedvezmeny_nem_ervenyes3() + { + var shop = new Shop(); + shop.RegisterProduct('A', 30); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 10); + shop.RegisterAmountDiscount('A', 5, 0.9,true); + shop.RegisterComboDiscount("ABC", 80, false); + shop.RegisterCountDiscount('A', 1, 5,true); + var price = shop.GetPrice("AAAAABCv123"); + price.Should().Be((30 + 20+10)*0.9); + } + + [Fact] + public void ures_kosar_dobjon_kivetelt() + { + var shop = new Shop(); + Action act = () => shop.GetPrice(""); + act.Should().Throw(); + } + + [Fact] + public void csak_meta_karakterek_dobjanak_kivetelt() + { + var shop = new Shop(); + Action act = () => shop.GetPrice("tpv123"); + act.Should().Throw(); + } + + [Fact] + public void ismeretlen_termekkod_dobjon_kivetelt() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + Action act = () => shop.GetPrice("AZ"); + act.Should().Throw(); + } + + [Fact] + public void darabos_kedvezmeny_hatarertekek() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterCountDiscount('A', 3, 4, false); + shop.GetPrice("AAA").Should().Be(30); + shop.GetPrice("AAAA").Should().Be(30); + shop.GetPrice("AAAAA").Should().Be(40); + } + + [Fact] + public void darabos_kedvezmeny_tobb_teljes_csoport() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterCountDiscount('A', 3, 4, false); + var price = shop.GetPrice("AAAAAAAA"); + price.Should().Be(60); + } + + [Fact] + public void kombo_tobbszor_is_alkalmazhato() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterComboDiscount("ABC", 60, false); + var price = shop.GetPrice("AABBCC"); + price.Should().Be(120); + } + + [Fact] + public void kombo_utan_maradek_termekek_is_helyesen_szamolodnak() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterComboDiscount("ABC", 60, false); + var price = shop.GetPrice("AABCC"); + price.Should().Be(120); + } + + [Fact] + public void egyedi_kupon(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterCoupon("112554", 0.9); + var price1 = shop.GetPrice("AABk112554"); + var price2 = shop.GetPrice("AABk112554"); + price1.Should().Be(40*0.9); + price2.Should().Be(40); + } + + [Fact] + + public void termek_db(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + var price = shop.GetPrice("A3B3C"); + price.Should().Be(3*10 + 3*20 + 50); + } + + [Fact] + public void termek_gramm(){ + var shop = new Shop(); + shop.RegisterProduct('A', 10,100); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50,200); + var price = shop.GetPrice("A300B3"); + price.Should().Be(300/100*10 + 3*20); + var price2 = shop.GetPrice("C300"); + price2.Should().Be(300/200*50); + } + + [Fact] +public void vasarlas_utan_a_leltar_frissul_a_megfelelo_darabszamokkal() +{ + var spyInventory = new SpyInventory(); + var shop = new Shop(spyInventory); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.GetPrice("AAB"); + spyInventory.UpdatedProducts['A'].Should().Be(-2); + spyInventory.UpdatedProducts['B'].Should().Be(-1); +} + + [Fact] +public void vasarlas_utan_a_termeket_visszahozza1() +{ + var costumer = new Costumer("v234"); + costumer.setwallet(1000); + var spyInventory = new SpyInventory(); + var shop = new Shop(spyInventory); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.Buy(costumer, "AAB"); + costumer.wallet.Should().Be(1000-40); + spyInventory.UpdatedProducts['A'].Should().Be(-2); + spyInventory.UpdatedProducts['B'].Should().Be(-1); + shop.ReturnProducts(costumer,"AB"); + spyInventory.UpdatedProducts['A'].Should().Be(-1); + spyInventory.UpdatedProducts['B'].Should().Be(0); + costumer.wallet.Should().Be(960+30); +} + + [Fact] +public void vasarlas_utan_a_termeket_visszahozza2() +{ + var costumer = new Costumer("v234"); + costumer.setwallet(1000); + var spyInventory = new SpyInventory(); + var shop = new Shop(spyInventory); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterComboDiscount("AB", 20, false); + shop.Buy(costumer, "AAB"); + costumer.wallet.Should().Be(1000-30); + spyInventory.UpdatedProducts['A'].Should().Be(-2); + spyInventory.UpdatedProducts['B'].Should().Be(-1); + shop.ReturnProducts(costumer,"AB"); + spyInventory.UpdatedProducts['A'].Should().Be(-1); + spyInventory.UpdatedProducts['B'].Should().Be(0); + costumer.wallet.Should().Be(970+20); +} + [Fact] +public void vasarlas_utan_a_termeket_visszahozza3() +{ + var costumer = new Costumer("v234"); + costumer.setwallet(1000); + var spyInventory = new SpyInventory(); + var shop = new Shop(spyInventory); + shop.RegisterProduct('A', 30); + shop.RegisterProduct('B',5); + shop.RegisterComboDiscount("AB", 17, false); + shop.RegisterAmountDiscount('A', 5, 0.5,false); + shop.Buy(costumer, "AABAAAA"); + costumer.wallet.Should().Be(1000-15*5-17); + spyInventory.UpdatedProducts['A'].Should().Be(-2); + spyInventory.UpdatedProducts['B'].Should().Be(-1); + shop.ReturnProducts(costumer,"AAB"); + spyInventory.UpdatedProducts['A'].Should().Be(-1); + spyInventory.UpdatedProducts['B'].Should().Be(0); + costumer.wallet.Should().Be(908+17+15); +} + +[Fact] +public void esemenyvezerelt1() +{ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('C', 20); + shop.Scan('A'); + shop.Scan('C'); + shop.Scan('A'); + var total = shop.Checkout(); + total.Should().Be(40); +} + +[Fact] +public void esemenyvezerelt2() +{ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterComboDiscount("ABC", 60, false); + shop.Scan('C'); + shop.Scan('A'); + shop.Scan('B'); + shop.Scan('A'); + shop.Checkout().Should().Be(60 + 10); +} + +[Fact] +public void esemenyvezerelt3() +{ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + + shop.Scan('A'); + shop.Scan('t'); + shop.Scan('A'); + shop.Checkout().Should().Be(18); +} + +[Fact] +public void checkout_utan_a_kosar_kiurul() +{ + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.Scan('A'); + shop.Checkout().Should().Be(10); + shop.Checkout().Should().Be(0); +} + +[Fact] +public void merleg1() +{ + var mockScale = Substitute.For(); + var shop = new Shop(mockScale); + shop.RegisterProduct('A', 10, 100); + mockScale.GetCurrentWeight().Returns(300); + shop.Scan('A'); + var total = shop.Checkout(); + total.Should().Be(30); + mockScale.Received(1).GetCurrentWeight(); +} + +[Fact] +public void merleg2() +{ + var mockScale = Substitute.For(); + var shop = new Shop(mockScale); + shop.RegisterProduct('B', 100); + shop.Scan('B'); + shop.Checkout(); + mockScale.DidNotReceive().GetCurrentWeight(); +} + +[Fact] +public void fizetes_esemeny_utan_a_fopenztaros_ertesul_a_vegosszegrol() +{ + + var mockNotifier = Substitute.For(); + var shop = new Shop(mockNotifier); + shop.RegisterProduct('A', 100); + shop.Scan('A'); + shop.Scan('A'); + mockNotifier.Received(1).NotifySuccessfulPayment(200); +} + +[Fact] +public void ures_kosar_eseten_nincs_fizetesi_ertesites() +{ + var mockNotifier = Substitute.For(); + var shop = new Shop(mockNotifier); + shop.ProcessCashPayment(); + mockNotifier.DidNotReceive().NotifySuccessfulPayment(Arg.Any()); +} + +[Fact] +public void sikeres_fizetes_utan_a_kosar_kiurul() +{ + + var mockNotifier = Substitute.For(); + var shop = new Shop(mockNotifier); + shop.RegisterProduct('A', 100); + shop.Scan('A'); + shop.ProcessCashPayment(); + shop.ProcessCashPayment(); + mockNotifier.Received(1).NotifySuccessfulPayment(100); +} + +[Fact] + public void pos_fizetes_gomb_meghivja_a_terminalt_a_megfelelo_osszeggel() + { + var mockNotifier = Substitute.For(); + var mockPos = Substitute.For(); + var shop = new Shop(mockNotifier, mockPos); + shop.RegisterProduct('A', 500); + shop.Scan('A'); + shop.ProcessPosPayment(); + mockPos.Received(1).StartPayment(500); + } + + [Fact] + public void sikeres_pos_visszaigazolas_eseten_lejelenti_a_fizetest_es_uriti_a_kosarat() + { + var mockNotifier = Substitute.For(); + var mockPos = Substitute.For(); + var shop = new Shop(mockNotifier, mockPos); + shop.RegisterProduct('A', 100); + shop.Scan('A'); + shop.ProcessPosPayment(); + shop.OnPaymentResult(true); + mockNotifier.Received(1).NotifySuccessfulPayment(100); + shop.ProcessCashPayment(); + mockNotifier.Received(1).NotifySuccessfulPayment(Arg.Any()); + } + + [Fact] + public void sikertelen_pos_visszaigazolas_eseten_nincs_jelentes_es_a_kosar_megmarad() + { + var mockNotifier = Substitute.For(); + var mockPos = Substitute.For(); + var shop = new Shop(mockNotifier, mockPos); + shop.RegisterProduct('A', 100); + shop.Scan('A'); + shop.ProcessPosPayment(); + shop.OnPaymentResult(false); + mockNotifier.DidNotReceive().NotifySuccessfulPayment(Arg.Any()); + shop.ProcessCashPayment(); + mockNotifier.Received(1).NotifySuccessfulPayment(100); + } + +[Fact] + public void storno_gomb_uriti_a_kosarat() + { + + var mockNotifier = Substitute.For(); + var shop = new Shop(mockNotifier); + shop.RegisterProduct('A', 100); + + shop.Scan('A'); + shop.Scan('A'); + + + shop.Storno(); + + + shop.ProcessCashPayment(); + + + mockNotifier.DidNotReceive().NotifySuccessfulPayment(Arg.Any()); + + + shop.Checkout().Should().Be(0); + } + + [Fact] + public void storno_eseten_a_leltarbol_nem_vonodik_le_a_termek() + { + + var spyInventory = new SpyInventory(); + var mockNotifier = Substitute.For(); + var shop = new Shop(mockNotifier, Substitute.For(), spyInventory); + + shop.RegisterProduct('A', 100); + + + shop.Scan('A'); + shop.Scan('A'); + shop.Storno(); + + shop.ProcessCashPayment(); + + + + + if (spyInventory.UpdatedProducts.ContainsKey('A')) + { + spyInventory.UpdatedProducts['A'].Should().Be(0); + } + } + + [Fact] + public void pos_fizetes_alatt_torteno_storno_megszakitja_a_folyamatot() + { + + var mockNotifier = Substitute.For(); + var mockPos = Substitute.For(); + var shop = new Shop(mockNotifier, mockPos); + shop.RegisterProduct('A', 100); + + shop.Scan('A'); + + + shop.ProcessPosPayment(); + shop.Storno(); + + + shop.OnPaymentResult(true); + + + + + mockNotifier.DidNotReceive().NotifySuccessfulPayment(Arg.Any()); + } + + [Fact] + public void napi_osszesites_keszpenzes_fizetes_utan_tartalmazza_a_darabszamot_es_bevetelt() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + + shop.Scan('A'); + shop.Scan('A'); + shop.Scan('B'); + shop.ProcessCashPayment(); + + var report = GetEndOfDaySummary(shop); + + report['A'].Quantity.Should().Be(2); + report['A'].Revenue.Should().Be(20); + report['B'].Quantity.Should().Be(1); + report['B'].Revenue.Should().Be(20); + } + + [Fact] + public void napi_osszesitesbe_csak_sikeres_ertekesites_szamit_storno_nem() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + + shop.Scan('A'); + shop.Storno(); + shop.ProcessCashPayment(); + + var report = GetEndOfDaySummary(shop); + report.Should().BeEmpty(); + } + + [Fact] + public void napi_osszesites_kombo_eseten_aranyosan_osztja_fel_a_bevetelt_listaarak_szerint() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterComboDiscount("ABC", 60, false); + + shop.Scan('A'); + shop.Scan('B'); + shop.Scan('C'); + shop.ProcessCashPayment(); + + var report = GetEndOfDaySummary(shop); + + report['A'].Quantity.Should().Be(1); + report['B'].Quantity.Should().Be(1); + report['C'].Quantity.Should().Be(1); + report['A'].Revenue.Should().BeApproximately(7.5, 0.0001); + report['B'].Revenue.Should().BeApproximately(15, 0.0001); + report['C'].Revenue.Should().BeApproximately(37.5, 0.0001); + } + + [Fact] + public void napi_osszesites_kombo_plusz_maradek_termekek_eseten_is_helyes() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.RegisterProduct('B', 20); + shop.RegisterProduct('C', 50); + shop.RegisterComboDiscount("ABC", 60, false); + + foreach (var item in "CAAAABB") + shop.Scan(item); + + shop.ProcessCashPayment(); + + var report = GetEndOfDaySummary(shop); + + report['A'].Quantity.Should().Be(4); + report['B'].Quantity.Should().Be(2); + report['C'].Quantity.Should().Be(1); + report['A'].Revenue.Should().BeApproximately(37.5, 0.0001); + report['B'].Revenue.Should().BeApproximately(35, 0.0001); + report['C'].Revenue.Should().BeApproximately(37.5, 0.0001); + } + + [Fact] + public void napi_osszesites_pos_hiba_utan_nem_no_de_kesobbi_sikeres_fizetes_utan_igen() + { + var notifier = Substitute.For(); + var pos = Substitute.For(); + var shop = new Shop(notifier, pos); + shop.RegisterProduct('A', 100); + + shop.Scan('A'); + shop.ProcessPosPayment(); + shop.OnPaymentResult(false); + GetEndOfDaySummary(shop).Should().BeEmpty(); + + shop.ProcessCashPayment(); + + var report = GetEndOfDaySummary(shop); + report['A'].Quantity.Should().Be(1); + report['A'].Revenue.Should().Be(100); + } + + [Fact] + public void napi_osszesites_lekerdezese_nem_nullazza_az_adatokat() + { + var shop = new Shop(); + shop.RegisterProduct('A', 10); + shop.Scan('A'); + shop.ProcessCashPayment(); + + var first = GetEndOfDaySummary(shop); + var second = GetEndOfDaySummary(shop); + + first['A'].Quantity.Should().Be(1); + first['A'].Revenue.Should().Be(10); + second['A'].Quantity.Should().Be(1); + second['A'].Revenue.Should().Be(10); + } + + [Fact] + public void crd_x02_kiskoru_es_illegalis_vasarlas_interfeszek_leteznek() + { + var minorNotifierType = GetRequiredCoreType("IMinorCustomerNotifier"); + var illegalNotifierType = GetRequiredCoreType("IIllegalPurchaseNotifier"); + + minorNotifierType.GetMethod("NotifyMinorCustomer", Type.EmptyTypes) + .Should().NotBeNull("kiskoru eseten kulon esemenyt kell jelezni"); + + illegalNotifierType.GetMethod("NotifyIllegalPurchase", new[] { typeof(char) }) + .Should().NotBeNull("illegalis vasarlas jelzeset interfeszen keresztul kell intezni"); + } + + [Fact] + public void crd_x02_a_shop_tud_kiskoru_ugyfelet_es_kiskorunak_tiltott_termeket_kezelni() + { + var minorNotifierType = GetRequiredCoreType("IMinorCustomerNotifier"); + var illegalNotifierType = GetRequiredCoreType("IIllegalPurchaseNotifier"); + + var ctor = typeof(Shop).GetConstructor(new[] { minorNotifierType, illegalNotifierType }); + ctor.Should().NotBeNull("a Shop kapjon ket kulon interfeszt a kiskoru es illegalis vasarlas esemenyekhez"); + + typeof(Shop).GetMethod("RegisterMinorRestrictedProduct", new[] { typeof(char) }) + .Should().NotBeNull("termekenkent beallithato legyen a kiskoru tiltasa"); + + typeof(Shop).GetMethod("SetMinorCustomer", new[] { typeof(bool) }) + .Should().NotBeNull("kulon esemeny jelzeshez lehessen beallitani, hogy a vasarlo kiskoru"); + } + + [Fact] + public void crd_x02_kiskoru_tiltott_termek_vasarlasa_illegalis_vasarlas_jelzest_kuld() + { + var minorNotifierType = GetRequiredCoreType("IMinorCustomerNotifier"); + var illegalNotifierType = GetRequiredCoreType("IIllegalPurchaseNotifier"); + + var minorNotifier = Substitute.For(new[] { minorNotifierType }, Array.Empty()); + var illegalNotifier = Substitute.For(new[] { illegalNotifierType }, Array.Empty()); + + var shop = CreateShopForMinorRules(minorNotifierType, illegalNotifierType, minorNotifier, illegalNotifier); + shop.RegisterProduct('A', 100); + + InvokeRequired(shop, "RegisterMinorRestrictedProduct", 'A'); + InvokeRequired(shop, "SetMinorCustomer", true); + shop.Scan('A'); + shop.ProcessCashPayment(); + + CountReceivedCallsByName(minorNotifier, "NotifyMinorCustomer").Should().BeGreaterThan(0); + CountReceivedCallsByName(illegalNotifier, "NotifyIllegalPurchase").Should().Be(1); + + var args = GetReceivedCallArgs(illegalNotifier, "NotifyIllegalPurchase"); + args.Should().ContainSingle(); + args[0].Should().BeOfType(); + ((char)args[0]).Should().Be('A'); + } + + [Fact] + public void crd_x02_nem_tiltott_termek_kiskorunal_nem_kuld_illegalis_jelzest() + { + var minorNotifierType = GetRequiredCoreType("IMinorCustomerNotifier"); + var illegalNotifierType = GetRequiredCoreType("IIllegalPurchaseNotifier"); + + var minorNotifier = Substitute.For(new[] { minorNotifierType }, Array.Empty()); + var illegalNotifier = Substitute.For(new[] { illegalNotifierType }, Array.Empty()); + + var shop = CreateShopForMinorRules(minorNotifierType, illegalNotifierType, minorNotifier, illegalNotifier); + shop.RegisterProduct('B', 100); + + InvokeRequired(shop, "SetMinorCustomer", true); + shop.Scan('B'); + shop.ProcessCashPayment(); + + CountReceivedCallsByName(illegalNotifier, "NotifyIllegalPurchase").Should().Be(0); + } + + [Fact] + public void crd_x02_nagykoru_tiltott_termeket_is_megveheti_illegalis_jelzes_nelkul() + { + var minorNotifierType = GetRequiredCoreType("IMinorCustomerNotifier"); + var illegalNotifierType = GetRequiredCoreType("IIllegalPurchaseNotifier"); + + var minorNotifier = Substitute.For(new[] { minorNotifierType }, Array.Empty()); + var illegalNotifier = Substitute.For(new[] { illegalNotifierType }, Array.Empty()); + + var shop = CreateShopForMinorRules(minorNotifierType, illegalNotifierType, minorNotifier, illegalNotifier); + shop.RegisterProduct('A', 100); + + InvokeRequired(shop, "RegisterMinorRestrictedProduct", 'A'); + InvokeRequired(shop, "SetMinorCustomer", false); + shop.Scan('A'); + shop.ProcessCashPayment(); + + CountReceivedCallsByName(illegalNotifier, "NotifyIllegalPurchase").Should().Be(0); + } + + private static Dictionary GetEndOfDaySummary(Shop shop) + { + var method = typeof(Shop).GetMethod("GetEndOfDaySummary"); + method.Should().NotBeNull("a Shop osztalynak tudnia kell nap vegi osszesitest adni"); + + var result = method!.Invoke(shop, null); + result.Should().NotBeNull(); + + var typed = result as Dictionary; + typed.Should().NotBeNull("a visszateresi tipus legyen Dictionary"); + return typed!; + } + + private static Type GetRequiredCoreType(string typeName) + { + var type = typeof(Shop).Assembly.GetType($"TddShop.Core.{typeName}"); + type.Should().NotBeNull($"a {typeName} interfesznek leteznie kell a TddShop.Core nevtérben"); + return type!; + } + + private static Shop CreateShopForMinorRules(Type minorNotifierType, Type illegalNotifierType, object minorNotifier, object illegalNotifier) + { + var ctor = typeof(Shop).GetConstructor(new[] { minorNotifierType, illegalNotifierType }); + ctor.Should().NotBeNull("a Shop-nak fogadnia kell a ket uj interfeszt"); + + var instance = ctor!.Invoke(new[] { minorNotifier, illegalNotifier }); + instance.Should().BeOfType(); + return (Shop)instance; + } + + private static object? InvokeRequired(object target, string methodName, params object[] args) + { + var argTypes = args.Select(a => a.GetType()).ToArray(); + var method = target.GetType().GetMethod(methodName, argTypes); + method.Should().NotBeNull($"a {methodName} API-nak leteznie kell"); + return method!.Invoke(target, args); + } + + private static int CountReceivedCallsByName(object substitute, string methodName) + { + return GetReceivedCalls(substitute).Count(c => GetMethodName(c) == methodName); + } + + private static object[] GetReceivedCallArgs(object substitute, string methodName) + { + var call = GetReceivedCalls(substitute).FirstOrDefault(c => GetMethodName(c) == methodName); + if (call is null) + return Array.Empty(); + + var getArguments = call.GetType().GetMethod("GetArguments", Type.EmptyTypes); + var args = getArguments?.Invoke(call, null) as object[]; + return args ?? Array.Empty(); + } + + private static List GetReceivedCalls(object substitute) + { + var extType = AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType("NSubstitute.SubstituteExtensions", false)) + .FirstOrDefault(t => t is not null); + + extType.Should().NotBeNull("a NSubstitute extension tipusnak elerhetonek kell lennie"); + + var receivedCallsMethod = extType! + .GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .FirstOrDefault(m => m.Name == "ReceivedCalls" && m.IsGenericMethodDefinition && m.GetParameters().Length == 1); + + receivedCallsMethod.Should().NotBeNull("a ReceivedCalls extension metodusnak leteznie kell"); + + var generic = receivedCallsMethod!.MakeGenericMethod(substitute.GetType()); + var calls = generic.Invoke(null, new[] { substitute }) as System.Collections.IEnumerable; + calls.Should().NotBeNull(); + + var result = new List(); + foreach (var call in calls!) + if (call is not null) + result.Add(call); + + return result; + } + + private static string GetMethodName(object call) + { + var method = call.GetType().GetMethod("GetMethodInfo", Type.EmptyTypes); + var methodInfo = method?.Invoke(call, null) as System.Reflection.MethodInfo; + return methodInfo?.Name ?? string.Empty; + } + +} + diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/SpyInventory.cs b/snippets/1011_cashregisterTdd/TddShop.Tests/SpyInventory.cs new file mode 100644 index 0000000..73975b7 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/SpyInventory.cs @@ -0,0 +1,13 @@ +using TddShop.Core; + +namespace TddShop.Tests; + +public class SpyInventory : IInventory +{ + public Dictionary UpdatedProducts { get; } = new(); + + public void Update(char product, int delta) + { + UpdatedProducts[product] = UpdatedProducts.GetValueOrDefault(product, 0) + delta; + } +} diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/TddShop.Tests.csproj b/snippets/1011_cashregisterTdd/TddShop.Tests/TddShop.Tests.csproj new file mode 100644 index 0000000..2c48689 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/TddShop.Tests.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/bin/Debug/net9.0/.msCoverageSourceRootsMapping_TddShop.Tests b/snippets/1011_cashregisterTdd/TddShop.Tests/bin/Debug/net9.0/.msCoverageSourceRootsMapping_TddShop.Tests new file mode 100644 index 0000000..97f08cc Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddShop.Tests/bin/Debug/net9.0/.msCoverageSourceRootsMapping_TddShop.Tests differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/bin/Debug/net9.0/CoverletSourceRootsMapping_TddShop.Tests b/snippets/1011_cashregisterTdd/TddShop.Tests/bin/Debug/net9.0/CoverletSourceRootsMapping_TddShop.Tests new file mode 100644 index 0000000..97f08cc Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddShop.Tests/bin/Debug/net9.0/CoverletSourceRootsMapping_TddShop.Tests differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.AssemblyInfo.cs b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.AssemblyInfo.cs new file mode 100644 index 0000000..e177fff --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("TddShop.Tests")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+300c11d3c747265803acf8f8447245bf782ee390")] +[assembly: System.Reflection.AssemblyProductAttribute("TddShop.Tests")] +[assembly: System.Reflection.AssemblyTitleAttribute("TddShop.Tests")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.AssemblyInfoInputs.cache b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.AssemblyInfoInputs.cache new file mode 100644 index 0000000..c85e74c --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +ba060cc3d4dec595d93256c56983facab9c2c69a261897fef377a92967eb7004 diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.GeneratedMSBuildEditorConfig.editorconfig b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..71c7a68 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = TddShop.Tests +build_property.ProjectDir = C:\önlab\snippets\snippets\1011_cashregisterTdd\TddShop.Tests\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.GlobalUsings.g.cs b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.GlobalUsings.g.cs new file mode 100644 index 0000000..2cd3d38 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.GlobalUsings.g.cs @@ -0,0 +1,9 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; +global using global::Xunit; diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.assets.cache b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.assets.cache new file mode 100644 index 0000000..e1172ac Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.assets.cache differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.csproj.AssemblyReference.cache b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.csproj.AssemblyReference.cache new file mode 100644 index 0000000..8062df7 Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/Debug/net9.0/TddShop.Tests.csproj.AssemblyReference.cache differ diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.dgspec.json b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.dgspec.json new file mode 100644 index 0000000..10a494e --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.dgspec.json @@ -0,0 +1,170 @@ +{ + "format": 1, + "restore": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj": {} + }, + "projects": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "projectName": "TddShop.Core", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj", + "projectName": "TddShop.Tests", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "FluentAssertions": { + "target": "Package", + "version": "[8.8.0, )" + }, + "Microsoft.NET.Test.Sdk": { + "target": "Package", + "version": "[17.12.0, )" + }, + "NSubstitute": { + "target": "Package", + "version": "[5.3.0, )" + }, + "coverlet.collector": { + "target": "Package", + "version": "[6.0.2, )" + }, + "xunit": { + "target": "Package", + "version": "[2.9.2, )" + }, + "xunit.runner.visualstudio": { + "target": "Package", + "version": "[2.8.2, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.g.props b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.g.props new file mode 100644 index 0000000..c69ef39 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.g.props @@ -0,0 +1,26 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Adam\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + + + + + + + + + C:\Users\Adam\.nuget\packages\xunit.analyzers\1.16.0 + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.g.targets b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.g.targets new file mode 100644 index 0000000..df5f78d --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/TddShop.Tests.csproj.nuget.g.targets @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/project.assets.json b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/project.assets.json new file mode 100644 index 0000000..017ed49 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/project.assets.json @@ -0,0 +1,1232 @@ +{ + "version": 3, + "targets": { + "net9.0": { + "Castle.Core/5.1.1": { + "type": "package", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + }, + "compile": { + "lib/net6.0/Castle.Core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Castle.Core.dll": { + "related": ".xml" + } + } + }, + "coverlet.collector/6.0.2": { + "type": "package", + "build": { + "build/netstandard2.0/coverlet.collector.targets": {} + } + }, + "FluentAssertions/8.8.0": { + "type": "package", + "compile": { + "lib/net6.0/FluentAssertions.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net6.0/FluentAssertions.dll": { + "related": ".pdb;.xml" + } + } + }, + "Microsoft.CodeCoverage/17.12.0": { + "type": "package", + "compile": { + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {} + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {} + }, + "build": { + "build/netstandard2.0/Microsoft.CodeCoverage.props": {}, + "build/netstandard2.0/Microsoft.CodeCoverage.targets": {} + } + }, + "Microsoft.NET.Test.Sdk/17.12.0": { + "type": "package", + "dependencies": { + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" + }, + "compile": { + "lib/netcoreapp3.1/_._": {} + }, + "runtime": { + "lib/netcoreapp3.1/_._": {} + }, + "build": { + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.props": {}, + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.targets": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/Microsoft.NET.Test.Sdk.props": {} + } + }, + "Microsoft.TestPlatform.ObjectModel/17.12.0": { + "type": "package", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + }, + "compile": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {} + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {} + }, + "resource": { + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.TestPlatform.TestHost/17.12.0": { + "type": "package", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.12.0", + "Newtonsoft.Json": "13.0.1" + }, + "compile": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {}, + "lib/netcoreapp3.1/testhost.dll": { + "related": ".deps.json" + } + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {}, + "lib/netcoreapp3.1/testhost.dll": { + "related": ".deps.json" + } + }, + "resource": { + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "zh-Hant" + } + }, + "build": { + "build/netcoreapp3.1/Microsoft.TestPlatform.TestHost.props": {} + } + }, + "Newtonsoft.Json/13.0.1": { + "type": "package", + "compile": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + } + }, + "NSubstitute/5.3.0": { + "type": "package", + "dependencies": { + "Castle.Core": "5.1.1" + }, + "compile": { + "lib/net6.0/NSubstitute.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/NSubstitute.dll": { + "related": ".xml" + } + } + }, + "System.Diagnostics.EventLog/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/System.Diagnostics.EventLog.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/System.Diagnostics.EventLog.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + }, + "runtimeTargets": { + "runtimes/win/lib/net6.0/System.Diagnostics.EventLog.Messages.dll": { + "assetType": "runtime", + "rid": "win" + }, + "runtimes/win/lib/net6.0/System.Diagnostics.EventLog.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Reflection.Metadata/1.6.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/System.Reflection.Metadata.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/System.Reflection.Metadata.dll": { + "related": ".xml" + } + } + }, + "xunit/2.9.2": { + "type": "package", + "dependencies": { + "xunit.analyzers": "1.16.0", + "xunit.assert": "2.9.2", + "xunit.core": "[2.9.2]" + } + }, + "xunit.abstractions/2.0.3": { + "type": "package", + "compile": { + "lib/netstandard2.0/xunit.abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/xunit.abstractions.dll": { + "related": ".xml" + } + } + }, + "xunit.analyzers/1.16.0": { + "type": "package" + }, + "xunit.assert/2.9.2": { + "type": "package", + "compile": { + "lib/net6.0/xunit.assert.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/xunit.assert.dll": { + "related": ".xml" + } + } + }, + "xunit.core/2.9.2": { + "type": "package", + "dependencies": { + "xunit.extensibility.core": "[2.9.2]", + "xunit.extensibility.execution": "[2.9.2]" + }, + "build": { + "build/xunit.core.props": {}, + "build/xunit.core.targets": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/xunit.core.props": {}, + "buildMultiTargeting/xunit.core.targets": {} + } + }, + "xunit.extensibility.core/2.9.2": { + "type": "package", + "dependencies": { + "xunit.abstractions": "2.0.3" + }, + "compile": { + "lib/netstandard1.1/xunit.core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard1.1/xunit.core.dll": { + "related": ".xml" + } + } + }, + "xunit.extensibility.execution/2.9.2": { + "type": "package", + "dependencies": { + "xunit.extensibility.core": "[2.9.2]" + }, + "compile": { + "lib/netstandard1.1/xunit.execution.dotnet.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard1.1/xunit.execution.dotnet.dll": { + "related": ".xml" + } + } + }, + "xunit.runner.visualstudio/2.8.2": { + "type": "package", + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/_._": {} + }, + "build": { + "build/net6.0/xunit.runner.visualstudio.props": {} + } + }, + "TddShop.Core/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "compile": { + "bin/placeholder/TddShop.Core.dll": {} + }, + "runtime": { + "bin/placeholder/TddShop.Core.dll": {} + } + } + } + }, + "libraries": { + "Castle.Core/5.1.1": { + "sha512": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "type": "package", + "path": "castle.core/5.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ASL - Apache Software Foundation License.txt", + "CHANGELOG.md", + "LICENSE", + "castle-logo.png", + "castle.core.5.1.1.nupkg.sha512", + "castle.core.nuspec", + "lib/net462/Castle.Core.dll", + "lib/net462/Castle.Core.xml", + "lib/net6.0/Castle.Core.dll", + "lib/net6.0/Castle.Core.xml", + "lib/netstandard2.0/Castle.Core.dll", + "lib/netstandard2.0/Castle.Core.xml", + "lib/netstandard2.1/Castle.Core.dll", + "lib/netstandard2.1/Castle.Core.xml", + "readme.txt" + ] + }, + "coverlet.collector/6.0.2": { + "sha512": "bJShQ6uWRTQ100ZeyiMqcFlhP7WJ+bCuabUs885dJiBEzMsJMSFr7BOyeCw4rgvQokteGi5rKQTlkhfQPUXg2A==", + "type": "package", + "path": "coverlet.collector/6.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "VSTestIntegration.md", + "build/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll", + "build/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "build/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "build/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "build/netstandard2.0/Microsoft.Extensions.FileSystemGlobbing.dll", + "build/netstandard2.0/Microsoft.TestPlatform.CoreUtilities.dll", + "build/netstandard2.0/Microsoft.TestPlatform.PlatformAbstractions.dll", + "build/netstandard2.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "build/netstandard2.0/Mono.Cecil.Mdb.dll", + "build/netstandard2.0/Mono.Cecil.Pdb.dll", + "build/netstandard2.0/Mono.Cecil.Rocks.dll", + "build/netstandard2.0/Mono.Cecil.dll", + "build/netstandard2.0/Newtonsoft.Json.dll", + "build/netstandard2.0/NuGet.Frameworks.dll", + "build/netstandard2.0/NuGet.Versioning.dll", + "build/netstandard2.0/System.Buffers.dll", + "build/netstandard2.0/System.Collections.Immutable.dll", + "build/netstandard2.0/System.Memory.dll", + "build/netstandard2.0/System.Numerics.Vectors.dll", + "build/netstandard2.0/System.Reflection.Metadata.dll", + "build/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", + "build/netstandard2.0/System.Text.Encodings.Web.dll", + "build/netstandard2.0/System.Text.Json.dll", + "build/netstandard2.0/System.Threading.Tasks.Extensions.dll", + "build/netstandard2.0/coverlet.collector.deps.json", + "build/netstandard2.0/coverlet.collector.dll", + "build/netstandard2.0/coverlet.collector.pdb", + "build/netstandard2.0/coverlet.collector.targets", + "build/netstandard2.0/coverlet.core.dll", + "build/netstandard2.0/coverlet.core.pdb", + "build/netstandard2.0/coverlet.core.xml", + "build/netstandard2.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "build/netstandard2.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "build/netstandard2.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "coverlet-icon.png", + "coverlet.collector.6.0.2.nupkg.sha512", + "coverlet.collector.nuspec" + ] + }, + "FluentAssertions/8.8.0": { + "sha512": "m0kwcqBwvVel03FuMa7Ozo/oTaxYbjeNlcOhQFkyQpwX/8wks6RNl/Jnn58DCZVs6c2oG1RsCZw7HfKSaxLm3w==", + "type": "package", + "path": "fluentassertions/8.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "FluentAssertions.png", + "LICENSE", + "fluentassertions.8.8.0.nupkg.sha512", + "fluentassertions.nuspec", + "lib/net47/FluentAssertions.dll", + "lib/net47/FluentAssertions.pdb", + "lib/net47/FluentAssertions.xml", + "lib/net6.0/FluentAssertions.dll", + "lib/net6.0/FluentAssertions.pdb", + "lib/net6.0/FluentAssertions.xml", + "lib/netstandard2.0/FluentAssertions.dll", + "lib/netstandard2.0/FluentAssertions.pdb", + "lib/netstandard2.0/FluentAssertions.xml", + "lib/netstandard2.1/FluentAssertions.dll", + "lib/netstandard2.1/FluentAssertions.pdb", + "lib/netstandard2.1/FluentAssertions.xml" + ] + }, + "Microsoft.CodeCoverage/17.12.0": { + "sha512": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==", + "type": "package", + "path": "microsoft.codecoverage/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "build/netstandard2.0/CodeCoverage/CodeCoverage.config", + "build/netstandard2.0/CodeCoverage/CodeCoverage.exe", + "build/netstandard2.0/CodeCoverage/Cov_x86.config", + "build/netstandard2.0/CodeCoverage/amd64/CodeCoverage.exe", + "build/netstandard2.0/CodeCoverage/amd64/Cov_x64.config", + "build/netstandard2.0/CodeCoverage/amd64/covrun64.dll", + "build/netstandard2.0/CodeCoverage/amd64/msdia140.dll", + "build/netstandard2.0/CodeCoverage/arm64/Cov_arm64.config", + "build/netstandard2.0/CodeCoverage/arm64/covrunarm64.dll", + "build/netstandard2.0/CodeCoverage/arm64/msdia140.dll", + "build/netstandard2.0/CodeCoverage/codecoveragemessages.dll", + "build/netstandard2.0/CodeCoverage/coreclr/Microsoft.VisualStudio.CodeCoverage.Shim.dll", + "build/netstandard2.0/CodeCoverage/covrun32.dll", + "build/netstandard2.0/CodeCoverage/msdia140.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Core.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Instrumentation.Core.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Instrumentation.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Interprocess.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.props", + "build/netstandard2.0/Microsoft.CodeCoverage.targets", + "build/netstandard2.0/Microsoft.DiaSymReader.dll", + "build/netstandard2.0/Microsoft.VisualStudio.TraceDataCollector.dll", + "build/netstandard2.0/Mono.Cecil.Pdb.dll", + "build/netstandard2.0/Mono.Cecil.Rocks.dll", + "build/netstandard2.0/Mono.Cecil.dll", + "build/netstandard2.0/ThirdPartyNotices.txt", + "build/netstandard2.0/alpine/x64/Cov_x64.config", + "build/netstandard2.0/alpine/x64/libCoverageInstrumentationMethod.so", + "build/netstandard2.0/alpine/x64/libInstrumentationEngine.so", + "build/netstandard2.0/arm64/MicrosoftInstrumentationEngine_arm64.dll", + "build/netstandard2.0/cs/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/de/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/es/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/fr/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/it/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ja/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ko/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/macos/x64/Cov_x64.config", + "build/netstandard2.0/macos/x64/libCoverageInstrumentationMethod.dylib", + "build/netstandard2.0/macos/x64/libInstrumentationEngine.dylib", + "build/netstandard2.0/pl/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/pt-BR/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ru/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/tr/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ubuntu/x64/Cov_x64.config", + "build/netstandard2.0/ubuntu/x64/libCoverageInstrumentationMethod.so", + "build/netstandard2.0/ubuntu/x64/libInstrumentationEngine.so", + "build/netstandard2.0/x64/MicrosoftInstrumentationEngine_x64.dll", + "build/netstandard2.0/x86/MicrosoftInstrumentationEngine_x86.dll", + "build/netstandard2.0/zh-Hans/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/zh-Hant/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "lib/net462/Microsoft.VisualStudio.CodeCoverage.Shim.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll", + "microsoft.codecoverage.17.12.0.nupkg.sha512", + "microsoft.codecoverage.nuspec" + ] + }, + "Microsoft.NET.Test.Sdk/17.12.0": { + "sha512": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", + "type": "package", + "path": "microsoft.net.test.sdk/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/net462/Microsoft.NET.Test.Sdk.props", + "build/net462/Microsoft.NET.Test.Sdk.targets", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.Program.cs", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.Program.fs", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.Program.vb", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.props", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.targets", + "buildMultiTargeting/Microsoft.NET.Test.Sdk.props", + "lib/net462/_._", + "lib/netcoreapp3.1/_._", + "microsoft.net.test.sdk.17.12.0.nupkg.sha512", + "microsoft.net.test.sdk.nuspec" + ] + }, + "Microsoft.TestPlatform.ObjectModel/17.12.0": { + "sha512": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "type": "package", + "path": "microsoft.testplatform.objectmodel/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "lib/net462/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/net462/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/net462/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/net462/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/netstandard2.0/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/netstandard2.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/netstandard2.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512", + "microsoft.testplatform.objectmodel.nuspec" + ] + }, + "Microsoft.TestPlatform.TestHost/17.12.0": { + "sha512": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", + "type": "package", + "path": "microsoft.testplatform.testhost/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "build/netcoreapp3.1/Microsoft.TestPlatform.TestHost.props", + "build/netcoreapp3.1/x64/testhost.dll", + "build/netcoreapp3.1/x64/testhost.exe", + "build/netcoreapp3.1/x86/testhost.x86.dll", + "build/netcoreapp3.1/x86/testhost.x86.exe", + "lib/net462/_._", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/testhost.deps.json", + "lib/netcoreapp3.1/testhost.dll", + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/x64/msdia140.dll", + "lib/netcoreapp3.1/x86/msdia140.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "microsoft.testplatform.testhost.17.12.0.nupkg.sha512", + "microsoft.testplatform.testhost.nuspec" + ] + }, + "Newtonsoft.Json/13.0.1": { + "sha512": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", + "type": "package", + "path": "newtonsoft.json/13.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "newtonsoft.json.13.0.1.nupkg.sha512", + "newtonsoft.json.nuspec", + "packageIcon.png" + ] + }, + "NSubstitute/5.3.0": { + "sha512": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", + "type": "package", + "path": "nsubstitute/5.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "icon.png", + "lib/net462/NSubstitute.dll", + "lib/net462/NSubstitute.xml", + "lib/net6.0/NSubstitute.dll", + "lib/net6.0/NSubstitute.xml", + "lib/netstandard2.0/NSubstitute.dll", + "lib/netstandard2.0/NSubstitute.xml", + "nsubstitute.5.3.0.nupkg.sha512", + "nsubstitute.nuspec" + ] + }, + "System.Diagnostics.EventLog/6.0.0": { + "sha512": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==", + "type": "package", + "path": "system.diagnostics.eventlog/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/System.Diagnostics.EventLog.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/System.Diagnostics.EventLog.dll", + "lib/net461/System.Diagnostics.EventLog.xml", + "lib/net6.0/System.Diagnostics.EventLog.dll", + "lib/net6.0/System.Diagnostics.EventLog.xml", + "lib/netcoreapp3.1/System.Diagnostics.EventLog.dll", + "lib/netcoreapp3.1/System.Diagnostics.EventLog.xml", + "lib/netstandard2.0/System.Diagnostics.EventLog.dll", + "lib/netstandard2.0/System.Diagnostics.EventLog.xml", + "runtimes/win/lib/net6.0/System.Diagnostics.EventLog.Messages.dll", + "runtimes/win/lib/net6.0/System.Diagnostics.EventLog.dll", + "runtimes/win/lib/net6.0/System.Diagnostics.EventLog.xml", + "runtimes/win/lib/netcoreapp3.1/System.Diagnostics.EventLog.Messages.dll", + "runtimes/win/lib/netcoreapp3.1/System.Diagnostics.EventLog.dll", + "runtimes/win/lib/netcoreapp3.1/System.Diagnostics.EventLog.xml", + "system.diagnostics.eventlog.6.0.0.nupkg.sha512", + "system.diagnostics.eventlog.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Reflection.Metadata/1.6.0": { + "sha512": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "type": "package", + "path": "system.reflection.metadata/1.6.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netstandard1.1/System.Reflection.Metadata.dll", + "lib/netstandard1.1/System.Reflection.Metadata.xml", + "lib/netstandard2.0/System.Reflection.Metadata.dll", + "lib/netstandard2.0/System.Reflection.Metadata.xml", + "lib/portable-net45+win8/System.Reflection.Metadata.dll", + "lib/portable-net45+win8/System.Reflection.Metadata.xml", + "system.reflection.metadata.1.6.0.nupkg.sha512", + "system.reflection.metadata.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "xunit/2.9.2": { + "sha512": "7LhFS2N9Z6Xgg8aE5lY95cneYivRMfRI8v+4PATa4S64D5Z/Plkg0qa8dTRHSiGRgVZ/CL2gEfJDE5AUhOX+2Q==", + "type": "package", + "path": "xunit/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "xunit.2.9.2.nupkg.sha512", + "xunit.nuspec" + ] + }, + "xunit.abstractions/2.0.3": { + "sha512": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==", + "type": "package", + "path": "xunit.abstractions/2.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net35/xunit.abstractions.dll", + "lib/net35/xunit.abstractions.xml", + "lib/netstandard1.0/xunit.abstractions.dll", + "lib/netstandard1.0/xunit.abstractions.xml", + "lib/netstandard2.0/xunit.abstractions.dll", + "lib/netstandard2.0/xunit.abstractions.xml", + "xunit.abstractions.2.0.3.nupkg.sha512", + "xunit.abstractions.nuspec" + ] + }, + "xunit.analyzers/1.16.0": { + "sha512": "hptYM7vGr46GUIgZt21YHO4rfuBAQS2eINbFo16CV/Dqq+24Tp+P5gDCACu1AbFfW4Sp/WRfDPSK8fmUUb8s0Q==", + "type": "package", + "path": "xunit.analyzers/1.16.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "analyzers/dotnet/cs/xunit.analyzers.dll", + "analyzers/dotnet/cs/xunit.analyzers.fixes.dll", + "tools/install.ps1", + "tools/uninstall.ps1", + "xunit.analyzers.1.16.0.nupkg.sha512", + "xunit.analyzers.nuspec" + ] + }, + "xunit.assert/2.9.2": { + "sha512": "QkNBAQG4pa66cholm28AxijBjrmki98/vsEh4Sx5iplzotvPgpiotcxqJQMRC8d7RV7nIT8ozh97957hDnZwsQ==", + "type": "package", + "path": "xunit.assert/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "lib/net6.0/xunit.assert.dll", + "lib/net6.0/xunit.assert.xml", + "lib/netstandard1.1/xunit.assert.dll", + "lib/netstandard1.1/xunit.assert.xml", + "xunit.assert.2.9.2.nupkg.sha512", + "xunit.assert.nuspec" + ] + }, + "xunit.core/2.9.2": { + "sha512": "O6RrNSdmZ0xgEn5kT927PNwog5vxTtKrWMihhhrT0Sg9jQ7iBDciYOwzBgP2krBEk5/GBXI18R1lKvmnxGcb4w==", + "type": "package", + "path": "xunit.core/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "build/xunit.core.props", + "build/xunit.core.targets", + "buildMultiTargeting/xunit.core.props", + "buildMultiTargeting/xunit.core.targets", + "xunit.core.2.9.2.nupkg.sha512", + "xunit.core.nuspec" + ] + }, + "xunit.extensibility.core/2.9.2": { + "sha512": "Ol+KlBJz1x8BrdnhN2DeOuLrr1I/cTwtHCggL9BvYqFuVd/TUSzxNT5O0NxCIXth30bsKxgMfdqLTcORtM52yQ==", + "type": "package", + "path": "xunit.extensibility.core/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "lib/net452/xunit.core.dll", + "lib/net452/xunit.core.dll.tdnet", + "lib/net452/xunit.core.xml", + "lib/net452/xunit.runner.tdnet.dll", + "lib/net452/xunit.runner.utility.net452.dll", + "lib/netstandard1.1/xunit.core.dll", + "lib/netstandard1.1/xunit.core.xml", + "xunit.extensibility.core.2.9.2.nupkg.sha512", + "xunit.extensibility.core.nuspec" + ] + }, + "xunit.extensibility.execution/2.9.2": { + "sha512": "rKMpq4GsIUIJibXuZoZ8lYp5EpROlnYaRpwu9Zr0sRZXE7JqJfEEbCsUriZqB+ByXCLFBJyjkTRULMdC+U566g==", + "type": "package", + "path": "xunit.extensibility.execution/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "lib/net452/xunit.execution.desktop.dll", + "lib/net452/xunit.execution.desktop.xml", + "lib/netstandard1.1/xunit.execution.dotnet.dll", + "lib/netstandard1.1/xunit.execution.dotnet.xml", + "xunit.extensibility.execution.2.9.2.nupkg.sha512", + "xunit.extensibility.execution.nuspec" + ] + }, + "xunit.runner.visualstudio/2.8.2": { + "sha512": "vm1tbfXhFmjFMUmS4M0J0ASXz3/U5XvXBa6DOQUL3fEz4Vt6YPhv+ESCarx6M6D+9kJkJYZKCNvJMas1+nVfmQ==", + "type": "package", + "path": "xunit.runner.visualstudio/2.8.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "build/net462/xunit.abstractions.dll", + "build/net462/xunit.runner.reporters.net452.dll", + "build/net462/xunit.runner.utility.net452.dll", + "build/net462/xunit.runner.visualstudio.props", + "build/net462/xunit.runner.visualstudio.testadapter.dll", + "build/net6.0/xunit.abstractions.dll", + "build/net6.0/xunit.runner.reporters.netcoreapp10.dll", + "build/net6.0/xunit.runner.utility.netcoreapp10.dll", + "build/net6.0/xunit.runner.visualstudio.props", + "build/net6.0/xunit.runner.visualstudio.testadapter.dll", + "lib/net462/_._", + "lib/net6.0/_._", + "xunit.runner.visualstudio.2.8.2.nupkg.sha512", + "xunit.runner.visualstudio.nuspec" + ] + }, + "TddShop.Core/1.0.0": { + "type": "project", + "path": "../TddShop.Core/TddShop.Core.csproj", + "msbuildProject": "../TddShop.Core/TddShop.Core.csproj" + } + }, + "projectFileDependencyGroups": { + "net9.0": [ + "FluentAssertions >= 8.8.0", + "Microsoft.NET.Test.Sdk >= 17.12.0", + "NSubstitute >= 5.3.0", + "TddShop.Core >= 1.0.0", + "coverlet.collector >= 6.0.2", + "xunit >= 2.9.2", + "xunit.runner.visualstudio >= 2.8.2" + ] + }, + "packageFolders": { + "C:\\Users\\Adam\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj", + "projectName": "TddShop.Tests", + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj", + "packagesPath": "C:\\Users\\Adam\\.nuget\\packages\\", + "outputPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\Adam\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj": { + "projectPath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Core\\TddShop.Core.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "FluentAssertions": { + "target": "Package", + "version": "[8.8.0, )" + }, + "Microsoft.NET.Test.Sdk": { + "target": "Package", + "version": "[17.12.0, )" + }, + "NSubstitute": { + "target": "Package", + "version": "[5.3.0, )" + }, + "coverlet.collector": { + "target": "Package", + "version": "[6.0.2, )" + }, + "xunit": { + "target": "Package", + "version": "[2.9.2, )" + }, + "xunit.runner.visualstudio": { + "target": "Package", + "version": "[2.8.2, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.Tests/obj/project.nuget.cache b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/project.nuget.cache new file mode 100644 index 0000000..48fa43e --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.Tests/obj/project.nuget.cache @@ -0,0 +1,28 @@ +{ + "version": 2, + "dgSpecHash": "2Wz4nmF0S5M=", + "success": true, + "projectFilePath": "C:\\önlab\\snippets\\snippets\\1011_cashregisterTdd\\TddShop.Tests\\TddShop.Tests.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Adam\\.nuget\\packages\\castle.core\\5.1.1\\castle.core.5.1.1.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\coverlet.collector\\6.0.2\\coverlet.collector.6.0.2.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\fluentassertions\\8.8.0\\fluentassertions.8.8.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\microsoft.codecoverage\\17.12.0\\microsoft.codecoverage.17.12.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\microsoft.net.test.sdk\\17.12.0\\microsoft.net.test.sdk.17.12.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\microsoft.testplatform.objectmodel\\17.12.0\\microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\microsoft.testplatform.testhost\\17.12.0\\microsoft.testplatform.testhost.17.12.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\nsubstitute\\5.3.0\\nsubstitute.5.3.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\system.diagnostics.eventlog\\6.0.0\\system.diagnostics.eventlog.6.0.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\system.reflection.metadata\\1.6.0\\system.reflection.metadata.1.6.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit\\2.9.2\\xunit.2.9.2.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.abstractions\\2.0.3\\xunit.abstractions.2.0.3.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.analyzers\\1.16.0\\xunit.analyzers.1.16.0.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.assert\\2.9.2\\xunit.assert.2.9.2.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.core\\2.9.2\\xunit.core.2.9.2.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.extensibility.core\\2.9.2\\xunit.extensibility.core.2.9.2.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.extensibility.execution\\2.9.2\\xunit.extensibility.execution.2.9.2.nupkg.sha512", + "C:\\Users\\Adam\\.nuget\\packages\\xunit.runner.visualstudio\\2.8.2\\xunit.runner.visualstudio.2.8.2.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/snippets/1011_cashregisterTdd/TddShop.sln b/snippets/1011_cashregisterTdd/TddShop.sln new file mode 100644 index 0000000..b09ca78 --- /dev/null +++ b/snippets/1011_cashregisterTdd/TddShop.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TddShop.Core", "TddShop.Core\TddShop.Core.csproj", "{A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TddShop.Tests", "TddShop.Tests\TddShop.Tests.csproj", "{6B87C278-892B-4130-8A40-33CE2E3E378D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TddShop.Gui", "TddShop.Gui\TddShop.Gui.csproj", "{D1CFACB8-568A-4F5A-BC64-CBA036550DF5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Debug|x64.Build.0 = Debug|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Debug|x86.Build.0 = Debug|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Release|Any CPU.Build.0 = Release|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Release|x64.ActiveCfg = Release|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Release|x64.Build.0 = Release|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Release|x86.ActiveCfg = Release|Any CPU + {A1AB98D8-1F89-41DF-894E-FD9BEDD086AA}.Release|x86.Build.0 = Release|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Debug|x64.ActiveCfg = Debug|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Debug|x64.Build.0 = Debug|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Debug|x86.ActiveCfg = Debug|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Debug|x86.Build.0 = Debug|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Release|Any CPU.Build.0 = Release|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Release|x64.ActiveCfg = Release|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Release|x64.Build.0 = Release|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Release|x86.ActiveCfg = Release|Any CPU + {6B87C278-892B-4130-8A40-33CE2E3E378D}.Release|x86.Build.0 = Release|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Debug|x64.Build.0 = Debug|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Debug|x86.Build.0 = Debug|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Release|Any CPU.Build.0 = Release|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Release|x64.ActiveCfg = Release|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Release|x64.Build.0 = Release|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Release|x86.ActiveCfg = Release|Any CPU + {D1CFACB8-568A-4F5A-BC64-CBA036550DF5}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/snippets/1011_cashregisterTdd/TddTemalabor_CRD.pdf b/snippets/1011_cashregisterTdd/TddTemalabor_CRD.pdf new file mode 100644 index 0000000..421fb64 Binary files /dev/null and b/snippets/1011_cashregisterTdd/TddTemalabor_CRD.pdf differ diff --git a/snippets/1011_cashregisterTdd/dokummentacio.txt b/snippets/1011_cashregisterTdd/dokummentacio.txt new file mode 100644 index 0000000..e69de29 diff --git a/snippets/1011_cashregisterTdd/index.md b/snippets/1011_cashregisterTdd/index.md new file mode 100644 index 0000000..c0f12c1 --- /dev/null +++ b/snippets/1011_cashregisterTdd/index.md @@ -0,0 +1,160 @@ +--- +layout: post +title: Okos Pénztárgép (Shop) fejlesztése TDD módszerrel +tags: tdd programming vibe-coding +author: Szakszon Ádám Dániel +--- + +# Pénztárgép Esettanulmány: Vibe kódolás kontrolláltan + +Ez az esettanulmány egy okos pénztárgép, vagyis Shop motor fejlesztését mutatja be C# nyelven. A fő cél nem csak egy helyes árkalkulátor megírása volt, hanem egy olyan domain motor létrehozása, amelyet a TDD módszerrel folyamatosan, biztonságosan lehetett bővíteni a változó üzleti igények mentén. + +> **Megjegyzés az olvasónak:** a történet megértéséhez hasznos a projekt gyökerében lévő feladatkiírás és a CRD-k, vagyis a Change Request Documentek ismerete is. Az alábbiakban a funkciók mellett hivatkozom a megfelelő CRD azonosítókra is, hogy az összefüggések egyértelműek legyenek. + +## Az alapfeladat + +A kiindulási követelmény nagyon egyszerű volt: létre kell hozni egy `Shop` osztályt, aminek megadhatók a termékek és az áraik. Egy terméket egyetlen nagybetű jelölt, az ár pedig egész szám volt. Ha a rendszer kapott egy kosarat stringként, például `"ABC"`, akkor vissza kellett adnia az összesített fizetendő árat. + +```csharp +Shop.RegisterProduct('A', 10); +Shop.RegisterProduct('C', 20); + +var price = Shop.GetPrice("AAC"); // 40 +``` + +## A mozgó célpont + +A kihívás ott kezdődött, hogy a megrendelői követelmények folyamatosan bővültek úgynevezett CRD-k formájában. Ezek új üzleti szabályokat hoztak be, és úgy kellett őket beépíteni, hogy a korábbi működés ne törjön el. + +Korai példa volt a CRD P01, amely mennyiségi kedvezményt vezetett be: ha egy termékből legalább öt darabot veszünk, akkor 10% kedvezmény jár az adott termékre. Ezt követték a "3-at fizet, 4-et vihet" típusú akciók, a komplex kombó kedvezmények, a klubtagságok és a kuponkódok. + +## Áttekintés és fejlesztési alapelvek + +A cél egy olyan, folyamatosan fejleszthető domain motor létrehozása volt, amelyben az új üzleti követelmények kis lépésekben, regresszió nélkül vezethetők be. + +A fejlesztés alapelve: + +1. Először piros tesztet írni az új CRD alapján. +2. AI asszisztenssel pontosan annyi implementációt készíttetni, hogy a teszt zöld legyen. +3. Folyamatosan futtatni a teljes tesztkészletet. + +Az eredmény egy olyan Shop motor lett, amely már nem csak árat számol, hanem eseményvezérelt pénztári folyamatokat, hardver-integrációkat, készletkezelést, napi riportot, valamint jogi és üzleti kontrollokat is támogat. + +## Használt technológiák + +* .NET 9 +* xUnit +* FluentAssertions +* NSubstitute +* WinForms, külön GUI kliens a kipróbáláshoz + +## Architektúra röviden + +### Projektek + +* `TddShop.Core`: üzleti logika, a `Shop` és a domain műveletek +* `TddShop.Tests`: automata tesztek, unit és viselkedési tesztek +* `TddShop.Gui`: grafikus kipróbáló felület + +### Integrációs interfészek + +A core réteg több külső függőséget interfészeken keresztül kezel. + +```csharp +public interface IInventory +{ + void Update(char product, int delta); +} + +public interface IScale +{ + double GetCurrentWeight(); +} + +public interface IPosTerminal +{ + void StartPayment(double amount); +} + +public interface IPaymentNotifier +{ + void NotifySuccessfulPayment(double amount); +} + +public interface IMinorCustomerNotifier +{ + void NotifyMinorCustomer(); +} + +public interface IIllegalPurchaseNotifier +{ + void NotifyIllegalPurchase(char product); +} +``` + +Ez a megoldás lehetővé teszi, hogy a program működését a valódi gépek nélkül is ellenőrizni lehessen. Így a tesztelés gyorsabb és biztosabb, mert elég a hardvereket szimulálni. + +## Funkcionális fejlődési út + +### 1. Kedvezményrendszer + +A rendszer egy rendkívül egyszerű árkalkulátorként indult, a kihívások pedig a kedvezményrendszerek bevezetésével kezdődtek. Első lépésként a mennyiségi kedvezményeket (CRD P01), majd a "3-at fizet, 4-et vihet" típusú darabos akciókat (CRD P02), végül az összetett kombó kedvezményeket (CRD P03) kellett implementálni. A nehézséget itt az adta, hogy ezek a kedvezmények sokszor versenyeztek egymással (CRD P07), amit egy minimálár-alapú döntési logikával és szigorú kombinációs szabályokkal oldottam meg. + +### 2. Meta-információk a kosárban + +Ezt követően a motor képessé vált a meta-információk kezelésére is anélkül, hogy a termékszámlálás felborult volna. Így került be a kosár-parsingba a klubtagság (CRD P04), a vásárlói azonosító és az ahhoz kötött pontgyűjtés (CRD P06, P08), valamint a kuponkódok beváltásának lehetősége (CRD P11). Szintén ehhez a szakaszhoz tartozott a vonalkód-beolvasás finomítása is: a rendszer megtanulta értelmezni a rövidített, darabszámos (CRD P12), illetve a tömegalapú (CRD P13) bemeneteket is. + +### 3. Eseményvezérelt pénztár üzemmód + +A legnagyobb architekturális ugrást az eseményvezérelt pénztár üzemmódra való átállás jelentette (CRD C01). Az egyszeri, stringes kiértékelést felváltotta egy valós kasszafolyamatot szimuláló API a Scan és Checkout metódusokkal. Ez az új struktúra tette lehetővé a külső hardverek, például a mérleg integrációját (CRD C02), a készlet valós idejű frissítését egy IInventory interfészen keresztül (CRD D01), és a fizetett összeget pontosan visszatérítő visszáru szakszerű kezelését (CRD D02). + +### 4. Fizetési folyamatok és üzleti kontrollok + +A projekt utolsó fázisában a fizetési folyamatok és az üzleti kontrollok kerültek fókuszba. Dedikált interfészeken keresztül valósult meg a készpénzes és a POS terminálos fizetés (CRD C03-C04), illetve bekerült a stornó funkció (CRD C05), amely a kosárfolyamat fizetés előtti, készletmozgás nélküli teljes megszakításáért felel. Végül a motor kibővült a nap végi riportok generálásával (CRD X01), valamint egy komoly jogi védelemmel: a kiskorúak vásárlásának állapotfüggő ellenőrzésével és a tiltott tranzakciók eseményalapú blokkolásával (CRD X02). + +## TDD működés közben + +### Miért volt fontos a TDD? + +1. A követelmények folyamatosan változtak. +2. Sok, egymást keresztező üzleti szabály jelent meg. +3. Magas volt a regresszióveszély, mert egy új CRD könnyen törhette volna a régi működést. + +### Minta ciklus + +1. Új követelményhez bukó teszt. +2. Minimális implementáció. +3. Teljes tesztfuttatás. +4. Szükség esetén refaktor. + +Ez tette lehetővé, hogy a rendszer 50+ tesztesetre bővülve is stabil maradjon. + +## Minőség és tesztelhetőség + +### Erősségek + +1. Interfész-alapú függőségek miatt jól mockolható. +2. Az eseményvezérelt működés jól tesztelhető állapotgépre bontható. +3. A kritikus pénzügyi logika több szinten fedett, az egyszerű, kombinált és edge case helyzetekre is. + +## Grafikus kipróbálás + +Készült egy külön WinForms kliens, amiben a fő funkciók kézzel kipróbálhatók: + +1. termékregisztráció +2. szkennelés +3. fizetés és stornó +4. napi összesítés +5. kiskorú és illegális vásárlás események + +Indítás: + +```bash +dotnet run --project TddShop.Gui/TddShop.Gui.csproj +``` + +## Összegzés + +Ez a projekt jól mutatja, hogy a TDD nem csak tesztelési technika, hanem tervezési eszköz is. A Shop motor egy kezdetben egyszerű árkalkulátorból fokozatosan egy valós üzleti szabályokat és külső integrációkat kezelő, stabilan fejleszthető rendszerré nőtt. + +A legfontosabb tanulság az, hogy amikor vibe codeolni szeretnénk, akkor a TDD vagy test-first módszer nagyon jó arra, hogy átlássuk, mi történik a kódunkban. Az AI fejlődésével már a teszteket is érdemes vele íratni, mert kevesebb hibát vét és gyorsabb, mint a manuális írás. Nekünk pedig mérnökként a pontos specifikációra, az architektúrára és a kimenet validálására kell fókuszálnunk. \ No newline at end of file