diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..1ff0c423
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..4ce6fdde
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,340 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
\ No newline at end of file
diff --git a/CashRegister.sln b/CashRegister.sln
new file mode 100644
index 00000000..1b36328e
--- /dev/null
+++ b/CashRegister.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29503.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashRegister", "CashRegister\CashRegister.csproj", "{A04E856B-69A9-41E4-B164-A2ACDFB2D709}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CashRegisterTests", "CashRegisterTests\CashRegisterTests.csproj", "{369B12A2-ACEE-48AF-BAA8-690EF1EAD6C1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A04E856B-69A9-41E4-B164-A2ACDFB2D709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A04E856B-69A9-41E4-B164-A2ACDFB2D709}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A04E856B-69A9-41E4-B164-A2ACDFB2D709}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A04E856B-69A9-41E4-B164-A2ACDFB2D709}.Release|Any CPU.Build.0 = Release|Any CPU
+ {369B12A2-ACEE-48AF-BAA8-690EF1EAD6C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {369B12A2-ACEE-48AF-BAA8-690EF1EAD6C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {369B12A2-ACEE-48AF-BAA8-690EF1EAD6C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {369B12A2-ACEE-48AF-BAA8-690EF1EAD6C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {84E678E1-0FBE-4993-B4DC-7A5354811E9D}
+ EndGlobalSection
+EndGlobal
diff --git a/CashRegister/CashRegister.csproj b/CashRegister/CashRegister.csproj
new file mode 100644
index 00000000..3f0d6a5c
--- /dev/null
+++ b/CashRegister/CashRegister.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ netcoreapp3.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CashRegister/CashRegister/Abstract/CashRegister.cs b/CashRegister/CashRegister/Abstract/CashRegister.cs
new file mode 100644
index 00000000..e7c34038
--- /dev/null
+++ b/CashRegister/CashRegister/Abstract/CashRegister.cs
@@ -0,0 +1,118 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace CashRegisterConsumer
+{
+ public abstract class CashRegister
+ {
+ private ICurrency _currency;
+ private decimal _price, _tender;
+ private int _transactionCount; // for future use and current exception handling
+ private ITenderStrategy _tenderStrategy;
+
+ public decimal PriceValue { get { return _price; } set { _price = value; } }
+ public decimal TenderValue { get { return _tender; } set { _tender = value; } }
+
+ public CashRegister(ICurrency currency, ITenderStrategy tenderStrategy)
+ {
+ RegisterCurrency(currency);
+ RegisterTenderStrategy(tenderStrategy);
+ }
+
+ public virtual void RegisterCurrency(ICurrency currency)
+ {
+ if (currency == null)
+ throw new NullReferenceException("Attempt to register a null currency is invalid.");
+ if (currency.AllDenominations.Count == 0)
+ throw new InvalidCurrencyException($"No denominations found in currency {currency.ToString()}");
+
+ this._currency = currency;
+ }
+
+ public virtual void RegisterTenderStrategy(ITenderStrategy tenderStrategy)
+ {
+ if (tenderStrategy == null)
+ throw new NullReferenceException("Attempt to register a null tender strategy is invalid.");
+
+ this._tenderStrategy = tenderStrategy;
+ }
+
+ public string Tender(string path)
+ {
+ // ensure that the path is not null
+ if (path == null || path == string.Empty)
+ throw new FileNotFoundException("Input file not found.", path);
+
+ // setup our stringbuilder for the response
+ StringBuilder tenderedValues = new StringBuilder();
+ try
+ {
+ // using to ensure stream closure
+ using (StreamReader sr = new StreamReader(path, System.Text.Encoding.UTF8))
+ {
+ // if the file is empty, throw format excpetion with message
+ if (sr.EndOfStream)
+ throw new FormatException($"The file {path} was empty");
+
+ while (!sr.EndOfStream)
+ {
+ // log transaction count (for exception handling)
+ _transactionCount++;
+ // setup the _price and _tender for this transaction
+ SetTransactionAmounts(sr);
+ // add the transaction calculation based on the strategy to the return string
+ var results = _tenderStrategy.Calculate(_currency, _price, _tender);
+ tenderedValues.Append(_tenderStrategy.Display(_currency));
+ };
+ }
+
+ // return the tendered value string for all transactions
+ return tenderedValues.ToString();
+ }
+ catch (FormatException e) // throw a generic message with a more specific inner message.
+ {
+ throw new FormatException($"The file {path} was not in the correct format", e);
+ }
+ catch (Exception e) // Is something missed?
+ {
+ throw e;
+ }
+ }
+
+ private void SetTransactionAmounts(StreamReader sr)
+ {
+ try
+ {
+ // reset for new transaction
+ _currency.Clear();
+ _price = _tender = 0;
+
+ // read the next line and set the _price and _tender
+ // NOTE: I used the Parse over TryParse to ensure non-numeric values throw an exception
+ var input = sr.ReadLine();
+ _price = Decimal.Parse(input.Split(",")[0]);
+ _tender = Decimal.Parse(input.Split(",")[1]);
+
+ if (_tender <= 0 || _price <= 0)
+ throw new InvalidCurrencyException($"Invalid negative currency. Please review line {_transactionCount} for errors.");
+
+ // ensure there is enough tender for the price. (they can be equal)
+ if (_tender < _price)
+ throw new NotEnoughTenderException($"Tender value less than price. Deficiency: {_price - _tender} on line {_transactionCount}");
+ }
+ catch (OverflowException e)
+ {
+ throw new InvalidCurrencyException($"Invalid input. Please review line {_transactionCount} for errors.", e);
+ }
+ catch (NotEnoughTenderException e)
+ {
+ throw new NotEnoughTenderException($"Please review line {_transactionCount} for errors", e);
+ }
+ catch (Exception e)
+ {
+ throw new FormatException(String.Format("{0} : Line {1}", e.Message, _transactionCount));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Concrete/POSCashRegister.cs b/CashRegister/CashRegister/Concrete/POSCashRegister.cs
new file mode 100644
index 00000000..15f1c669
--- /dev/null
+++ b/CashRegister/CashRegister/Concrete/POSCashRegister.cs
@@ -0,0 +1,9 @@
+namespace CashRegisterConsumer
+{
+ public class POSCashRegister : CashRegister
+ {
+ public POSCashRegister(ICurrency currency, ITenderStrategy tenderStrategy) : base(currency, tenderStrategy)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Currencies/Abstract/Currency.cs b/CashRegister/CashRegister/Currencies/Abstract/Currency.cs
new file mode 100644
index 00000000..607769ad
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Abstract/Currency.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace CashRegisterConsumer
+{
+ public abstract class Currency : ICurrency
+ {
+ protected List _bills;
+ protected List _coins;
+
+ public List Bills { get { return _bills; } }
+ public List Coins { get { return _coins; } }
+ public List AllDenominations { get { return _bills.Concat(_coins).ToList(); } }
+
+ public Currency()
+ {
+ // initialize lists
+ this._bills = new List();
+ this._coins = new List();
+
+ InitializeCurrency(); // load currency "money" based on inherited implementation
+ this._bills.Sort(); // ensure that we have the correct order of money
+ this._bills.Reverse(); // since the sort will make it small to large, we want large to small so foreach works easier.
+ this._coins.Sort();
+ this._coins.Reverse();
+ }
+
+ protected abstract void InitializeCurrency();
+
+ public void Clear()
+ {
+ foreach (Money money in AllDenominations)
+ {
+ money.Clear();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Currencies/Abstract/ICurrency.cs b/CashRegister/CashRegister/Currencies/Abstract/ICurrency.cs
new file mode 100644
index 00000000..9ae31a45
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Abstract/ICurrency.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace CashRegisterConsumer
+{
+ public interface ICurrency
+ {
+ List Bills { get; }
+ List Coins { get; }
+ List AllDenominations { get; }
+
+ void Clear();
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Currencies/Abstract/IMoney.cs b/CashRegister/CashRegister/Currencies/Abstract/IMoney.cs
new file mode 100644
index 00000000..72cbcdb2
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Abstract/IMoney.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CashRegisterConsumer
+{
+ public interface IMoney
+ {
+ void Add(int count);
+ void Subtract(int count);
+ void Clear();
+ }
+}
diff --git a/CashRegister/CashRegister/Currencies/Abstract/Money.cs b/CashRegister/CashRegister/Currencies/Abstract/Money.cs
new file mode 100644
index 00000000..bcfe509d
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Abstract/Money.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace CashRegisterConsumer
+{
+ public abstract class Money : IComparable
+ {
+ private int _count;
+
+ private readonly decimal _denomination;
+ private readonly string _singleName;
+ private readonly string _pluralName;
+
+ public decimal Denomination { get { return _denomination; } }
+ public string Name { get { return (_count == 1) ? _singleName : _pluralName; } }
+ public int Count { get { return _count; } }
+
+ public Money(decimal denomination, string singleName, string pluralName)
+ {
+ this._denomination = denomination;
+ this._singleName = singleName;
+ this._pluralName = pluralName;
+ this._count = 0;
+ }
+
+ public Money(decimal denomination, string singleName, string pluralName, int count) : this(denomination, singleName, pluralName)
+ {
+ this._count = count;
+ }
+
+ public void Add(int count)
+ {
+ _count += count;
+ }
+
+ public void Subtract(int count)
+ {
+ _count -= count;
+ }
+
+ public void Clear()
+ {
+ _count = 0;
+ }
+
+ ///
+ /// IComparable override. Used for sorting and reversing the currency to ensure the list of Money is
+ /// from largest denomination to smallest.
+ ///
+ ///
+ ///
+ public int CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ Money otherMoney = other as Money;
+ if (otherMoney != null)
+ return this._denomination.CompareTo(otherMoney._denomination);
+ else
+ throw new ArgumentException("Object is not Money");
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Currencies/Concrete/Bill.cs b/CashRegister/CashRegister/Currencies/Concrete/Bill.cs
new file mode 100644
index 00000000..ffc641f1
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Concrete/Bill.cs
@@ -0,0 +1,8 @@
+namespace CashRegisterConsumer
+{
+ public class Bill : Money
+ {
+ public Bill(decimal denomination, string singleName, string pluralName) : base(denomination, singleName, pluralName) { }
+ public Bill(decimal denomination, string singleName, string pluralName, int count) : base(denomination, singleName, pluralName, count) { }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Currencies/Concrete/Coin.cs b/CashRegister/CashRegister/Currencies/Concrete/Coin.cs
new file mode 100644
index 00000000..2bff0858
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Concrete/Coin.cs
@@ -0,0 +1,9 @@
+namespace CashRegisterConsumer
+{
+ public class Coin : Money
+ {
+ public Coin(decimal denomination, string singleName, string pluralName) : base(denomination, singleName, pluralName) { }
+ public Coin(decimal denomination, string singleName, string pluralName, int count) : base(denomination, singleName, pluralName, count) { }
+
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Currencies/Concrete/USD.cs b/CashRegister/CashRegister/Currencies/Concrete/USD.cs
new file mode 100644
index 00000000..d4d810ee
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Concrete/USD.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+
+namespace CashRegisterConsumer
+{
+ internal class USD : Currency
+ {
+ public USD() : base() { }
+
+ protected override void InitializeCurrency()
+ {
+ this._bills = new List() {
+ new Bill(100000, "one hundred thousand", "one hundred thousands"),
+ new Bill(10000, "ten thousand", "ten thousands"),
+ new Bill(5000, "five thousand", "five thousands"),
+ new Bill(1000, "thousand", "thousands"),
+ new Bill(500, "five hundred", "five hundreds"),
+ new Bill(100, "hundred", "hundreds"),
+ new Bill(50, "fifty", "fifties"),
+ new Bill(20, "twenty", "twenties"),
+ new Bill(10, "ten", "tens"),
+ new Bill(5, "five", "fives"),
+ new Bill(1, "dollar", "dollars")
+ };
+
+ this._coins = new List() {
+ new Coin(.25m, "quarter", "quarters"),
+ new Coin(.10m, "dime", "dimes"),
+ new Coin(.05m, "nickel", "nickels"),
+ new Coin(.01m, "penny","pennies")
+ };
+ }
+ }
+}
diff --git a/CashRegister/CashRegister/Currencies/Concrete/YEN.cs b/CashRegister/CashRegister/Currencies/Concrete/YEN.cs
new file mode 100644
index 00000000..2738a91a
--- /dev/null
+++ b/CashRegister/CashRegister/Currencies/Concrete/YEN.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace CashRegisterConsumer
+{
+ class YEN : Currency
+ {
+ public YEN() : base() { }
+ protected override void InitializeCurrency()
+ {
+ this._bills = new List() {
+ new Bill(10000, "10000-yen note", "10000-yen notes"),
+ new Bill(5000, "5000-yen note", "5000-yen notes"),
+ new Bill(2000, "2000-yen note", "2000-yen notes"),
+ new Bill(1000, "1000-yen note", "1000-yen notes")
+ };
+
+ this._coins = new List() {
+ new Coin(500, "500-yen coin", "500-yen coins"),
+ new Coin(100, "100-yen coin", "100-yen coins"),
+ new Coin(50, "50-yen coin", "50-yen coins"),
+ new Coin(10, "10-yen coin", "10-yen coins"),
+ new Coin(5, "5-yen coin", "5-yen coins"),
+ new Coin(1, "1-yen coin","1-yen coins")
+ };
+ }
+ }
+}
diff --git a/CashRegister/CashRegister/Strategies/Abstract/ITenderStrategy.cs b/CashRegister/CashRegister/Strategies/Abstract/ITenderStrategy.cs
new file mode 100644
index 00000000..9064aba1
--- /dev/null
+++ b/CashRegister/CashRegister/Strategies/Abstract/ITenderStrategy.cs
@@ -0,0 +1,9 @@
+namespace CashRegisterConsumer
+{
+ public interface ITenderStrategy
+ {
+ ICurrency Calculate(ICurrency currency, decimal price, decimal tender);
+
+ string Display(ICurrency currency);
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Strategies/Abstract/TenderStrategy.cs b/CashRegister/CashRegister/Strategies/Abstract/TenderStrategy.cs
new file mode 100644
index 00000000..939e11d7
--- /dev/null
+++ b/CashRegister/CashRegister/Strategies/Abstract/TenderStrategy.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace CashRegisterConsumer
+{
+ public abstract class TenderStrategy : ITenderStrategy
+ {
+ public virtual string Display(ICurrency currency)
+ {
+ // METHOD 1 - with foreach loop (did not remove code so reviewers could evaluate both methods)
+ //StringBuilder sr = new StringBuilder();
+ //foreach (Money money in currency.AllDenominations)
+ //{
+ // if (money.Count > 0)
+ // {
+ // sr.Append(String.Format("{0} {1},", money.Count, money.Name));
+ // }
+ //}
+
+ // METHOD 2 - Using LINQ.. both one line and 2 line versions. 1 line version commented out due to complexity and clarity
+ var money = currency.AllDenominations.Where(x => x.Count > 0);
+ var sr = money.Aggregate(new StringBuilder(), (x, y) => x.Append(String.Format("{0} {1},", y.Count, y.Name)));
+ //var sr = currency.AllDenominations.Where(x => x.Count > 0).Aggregate(new StringBuilder(), (x, y) => x.Append(String.Format("{0} {1},", y.Count, y.Name)));
+
+ if (sr.Length == 0)
+ return String.Format("{0}\n", "No Change Due"); // not part of the requirements, yet exact change is a viable value.
+ else
+ return String.Format("{0}\n", sr.ToString().Trim(','));
+ }
+
+ public virtual ICurrency Calculate(ICurrency currency, decimal price, decimal tender)
+ {
+ if (currency.AllDenominations.Count == 0)
+ throw new InvalidCurrencyException("No currency denominations found");
+
+ decimal change = tender - price;
+
+ // the currency.AllDenominations.Min(x => x.Denomination) is to ensure that if there is a currency that
+ // has a minimum value that is less then that change, the extra will be "dropped" and no infinite loop will occur.
+ // This itself has the problem that in a large system, these "dropped" percentages could be significant. This would
+ // be addressed with the business and the development team to determine the best course of action.
+ while (change >= currency.AllDenominations.Min(x => x.Denomination))
+ {
+ foreach (Money money in currency.AllDenominations)
+ {
+ while (change >= money.Denomination)
+ {
+ money.Add(1);
+ change -= money.Denomination;
+ }
+ }
+ }
+ return currency;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Strategies/Concrete/Random3Strategy.cs b/CashRegister/CashRegister/Strategies/Concrete/Random3Strategy.cs
new file mode 100644
index 00000000..0161a7ea
--- /dev/null
+++ b/CashRegister/CashRegister/Strategies/Concrete/Random3Strategy.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Linq;
+
+namespace CashRegisterConsumer
+{
+ public class Random3Strategy : TenderStrategy
+ {
+ public override ICurrency Calculate(ICurrency currency, decimal price, decimal tender)
+ {
+ if (currency.AllDenominations.Count == 0)
+ throw new InvalidCurrencyException("No currency denominations found");
+
+ if (Math.Abs(((price * 100) % 3)) == 0) // updated to correct algorithm
+ {
+ Random random = new Random();
+ int count;
+ decimal change = tender - price;
+
+ // the currency.AllDenominations.Min(x => x.Denomination) is to ensure that if there is a currency that
+ // has a minimum value that is less then that change, the extra will be "dropped" and no infinite loop will occur.
+ // This itself has the problem that in a large system, these "dropped" percentages could be significant. This would
+ // be addressed with the business and the development team to determine the best course of action.
+ while (change >= currency.AllDenominations.Min(x => x.Denomination))
+ {
+ foreach (Money money in currency.AllDenominations)
+ {
+ if (money.Denomination <= change) // if the current denomination is less than the change
+ {
+ count = random.Next(1, (int)Math.Floor(change / money.Denomination)); // get a random count (not bigger than the change)
+ money.Add(count); // add the appropriate amount of this denomination based on our random
+ change -= (money.Denomination * count); // remove the money denomination(times count) from the change
+ }
+ }
+ }
+ return currency;
+ }
+ else
+ {
+ return base.Calculate(currency, price, tender);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegister/Strategies/Concrete/StandardTenderStrategy.cs b/CashRegister/CashRegister/Strategies/Concrete/StandardTenderStrategy.cs
new file mode 100644
index 00000000..2dc77065
--- /dev/null
+++ b/CashRegister/CashRegister/Strategies/Concrete/StandardTenderStrategy.cs
@@ -0,0 +1,12 @@
+using System.Linq;
+
+namespace CashRegisterConsumer
+{
+ public class StandardTenderStrategy : TenderStrategy
+ {
+ public override ICurrency Calculate(ICurrency currency, decimal price, decimal tender)
+ {
+ return base.Calculate(currency, price, tender);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/CashRegisterInputFiles/USD Input File.txt b/CashRegister/CashRegisterInputFiles/USD Input File.txt
new file mode 100644
index 00000000..fe2de4c4
--- /dev/null
+++ b/CashRegister/CashRegisterInputFiles/USD Input File.txt
@@ -0,0 +1,10 @@
+2.12,3.00
+1.97,2.00
+3.33,5.00
+5.00,5.00
+8.83,1470.30
+20.57,76.55
+3.91,71.61
+48.31,78.81
+54.63,85.09
+62.33,95.61
\ No newline at end of file
diff --git a/CashRegister/CashRegisterInputFiles/YEN Input File.txt b/CashRegister/CashRegisterInputFiles/YEN Input File.txt
new file mode 100644
index 00000000..59bef7ac
--- /dev/null
+++ b/CashRegister/CashRegisterInputFiles/YEN Input File.txt
@@ -0,0 +1,4 @@
+5000, 10000
+100, 1000
+350, 500
+120, 12000
\ No newline at end of file
diff --git a/CashRegister/Custom Exceptions/InvalidCurrencyException.cs b/CashRegister/Custom Exceptions/InvalidCurrencyException.cs
new file mode 100644
index 00000000..4e564e61
--- /dev/null
+++ b/CashRegister/Custom Exceptions/InvalidCurrencyException.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace CashRegisterConsumer
+{
+ ///
+ /// Custom exception for clarity of code and exception handling
+ ///
+ [Serializable]
+ public class InvalidCurrencyException : Exception
+ {
+ public InvalidCurrencyException()
+ {
+ }
+
+ public InvalidCurrencyException(string message) : base(message)
+ {
+ }
+
+ public InvalidCurrencyException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidCurrencyException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/Custom Exceptions/NotEnoughTenderException.cs b/CashRegister/Custom Exceptions/NotEnoughTenderException.cs
new file mode 100644
index 00000000..4a04b778
--- /dev/null
+++ b/CashRegister/Custom Exceptions/NotEnoughTenderException.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace CashRegisterConsumer
+{
+ ///
+ /// Custom exception for clarity of code and exception handling
+ ///
+ [Serializable]
+ public class NotEnoughTenderException : Exception
+ {
+ public NotEnoughTenderException()
+ {
+ }
+
+ public NotEnoughTenderException(string message) : base(message)
+ {
+ }
+
+ public NotEnoughTenderException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected NotEnoughTenderException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/Preface.txt b/CashRegister/Preface.txt
new file mode 100644
index 00000000..5a9ce9ff
--- /dev/null
+++ b/CashRegister/Preface.txt
@@ -0,0 +1,20 @@
+I have created this project with the idea that the console is the consumer of a CashRegister system that allows for the injection
+of both the Currency type (USD, YEN, etc...) along with the strategy for change calculation. Future development would be able to allow
+for different strategies to be injected using IOC containers in a configuration to quickly change the functionality and currency of the
+CashRegister system.
+
+I have put more comments in this code than I normally do in production code as too many comments can be a code smell. I understand that
+comments are important, but are best used sparingly. It is more important to use correct naming conventions and clear code that can be read
+easily, then to trash code with a bunch of comments that with time may not be updated to reflect the actual working of the software. Code itself
+should be the documentation.
+
+If there are any questions or comments please email me at plwest.axiom@gmail.com and I will respond at the earliest possible time. I can also provide
+you with photos of my paperwork that includes my initial design notes, revision notes, and just my thoughts as I was working through this project.
+
+I appreciate your evaluation and giving me this oppritunity. Thank you.
+
+
+
+Press any button to continue with using the CashRegister Module. (Simulates POS ""Tender"" action).
+
+
diff --git a/CashRegister/Program.cs b/CashRegister/Program.cs
new file mode 100644
index 00000000..d992646e
--- /dev/null
+++ b/CashRegister/Program.cs
@@ -0,0 +1,46 @@
+using System;
+using System.IO;
+
+namespace CashRegisterConsumer
+{
+ internal class Program
+ {
+ private static readonly string prefaceFilePath = @"C:\Users\plwes\source\repos\CashRegister\CashRegister\Preface.txt";
+ private static readonly string usdInputFilePath = @"C:\Users\plwes\source\repos\CashRegister\CashRegister\CashRegisterInputFiles\USD Input File.txt";
+ private static readonly string yenInputFilePath = @"C:\Users\plwes\source\repos\CashRegister\CashRegister\CashRegisterInputFiles\YEN Input File.txt";
+ private static void Main(string[] args)
+ {
+ using (StreamReader sr = new StreamReader(prefaceFilePath, System.Text.Encoding.UTF8))
+ {
+ Console.SetWindowSize(150, 25);
+ Console.WriteLine(sr.ReadToEnd());
+ }
+
+ Console.ReadKey();
+
+ try
+ {
+ CashRegister register = new POSCashRegister(new USD(), new Random3Strategy());
+ var result = register.Tender(usdInputFilePath);
+ Console.WriteLine(result);
+
+
+ // for example (change the register to use YEN with a Standard Tender Strategy note that the demoniations are different so
+ // the same files that use "USD" decimals will be off. There are no decimals in YEN
+ register.RegisterCurrency(new YEN());
+ register.RegisterTenderStrategy(new StandardTenderStrategy());
+ result = register.Tender(yenInputFilePath);
+ Console.WriteLine(result);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+ if (e.InnerException != null)
+ Console.WriteLine(e.InnerException.Message);
+ }
+
+
+ Console.ReadKey();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CashRegister/_diagramCashRegister.cd b/CashRegister/_diagramCashRegister.cd
new file mode 100644
index 00000000..eb6c679a
--- /dev/null
+++ b/CashRegister/_diagramCashRegister.cd
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+ AgAAAAAAAAAAEAAAAIAAIABBAAAIAAAAgIAAAEAAAAg=
+ CashRegister\Abstract\CashRegister.cs
+
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ CashRegister\Concrete\POSCashRegister.cs
+
+
+
+
+
+ AAAABAAAAAAAAAAAAAAADAAACAAAAAAAAAAACABEAAA=
+ CashRegister\Currencies\Abstract\Currency.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ABIAAAAAAAAAAEQAAAACAAQAAAAAAAAABACAAABBAAA=
+ CashRegister\Currencies\Abstract\Money.cs
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ CashRegister\Currencies\Concrete\Bill.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ CashRegister\Currencies\Concrete\Coin.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAA=
+ CashRegister\Currencies\Concrete\USD.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAA=
+ CashRegister\Currencies\Concrete\YEN.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAQAAACAAAAAAAAAAAAAAAAA=
+ CashRegister\Strategies\Abstract\TenderStrategy.cs
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAA=
+ CashRegister\Strategies\Concrete\Random3Strategy.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAA=
+ CashRegister\Strategies\Concrete\StandardTenderStrategy.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ Custom Exceptions\InvalidCurrencyException.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ Custom Exceptions\NotEnoughTenderException.cs
+
+
+
+
+
+ AAAABAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAABAAAA=
+ CashRegister\Currencies\Abstract\ICurrency.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAQAAACAAAAAAAAAAAAAAAAA=
+ CashRegister\Strategies\Abstract\ITenderStrategy.cs
+
+
+
+
\ No newline at end of file
diff --git a/CashRegisterTests/CashRegisterTests.cs b/CashRegisterTests/CashRegisterTests.cs
new file mode 100644
index 00000000..e5cbdd0f
--- /dev/null
+++ b/CashRegisterTests/CashRegisterTests.cs
@@ -0,0 +1,131 @@
+using CashRegisterConsumer;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Xunit;
+
+namespace CashRegisterTests
+{
+ public class CashRegisterTests
+ {
+ #region SETUP
+
+ private readonly Mock currencyMock;
+ private readonly Mock tenderStrategyMock;
+
+ private readonly string TenderValueTestFile = @"C:\Users\plwes\source\repos\CashRegister\CashRegisterTests\Test Input Files\ValueTests\TenderValueTestFile.txt";
+ private readonly string PriceValueTestFile = @"C:\Users\plwes\source\repos\CashRegister\CashRegisterTests\Test Input Files\ValueTests\PriceValueTestFile.txt";
+ private readonly string EmptyFile = @"C:\Users\plwes\source\repos\CashRegister\CashRegisterTests\Test Input Files\EmptyFile.txt";
+ private readonly string EmptyLineFile = @"C:\Users\plwes\source\repos\CashRegister\CashRegisterTests\Test Input Files\EmptyLineFile.txt";
+ private readonly string NotEnoughTenderFile = @"C:\Users\plwes\source\repos\CashRegister\CashRegisterTests\Test Input Files\TenderLessThanPrice.txt";
+ private readonly string OverflowFile = @"C:\Users\plwes\source\repos\CashRegister\CashRegisterTests\Test Input Files\ValueTests\OverflowTransactionTestFile.txt";
+ public CashRegisterTests()
+ {
+ currencyMock = new Mock();
+ tenderStrategyMock = new Mock();
+ }
+
+ #endregion SETUP
+
+ [Fact]
+ public void CashRegisterPriceValueIsAccuratelySetBasedOnTextFileInput()
+ {
+ // setup
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Coin(.25m, "testCoin", "testCoins") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+
+ // setup excpeted
+ decimal expected = 2.45m;
+ // execute to a point where the actual can be tested
+ var results = register.Tender(PriceValueTestFile);
+ // are they equal????
+ Assert.Equal(expected, register.PriceValue);
+ }
+
+ [Fact]
+ public void CashRegisterTenderValueIsAccuratelySetBasedOnTextFileInput()
+ {
+ // setup
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Coin(.25m, "testCoin", "testCoins") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+
+ // setup excpeted
+ decimal expected = 110.98m;
+ // execute to a point where the actual can be tested
+ var results = register.Tender(TenderValueTestFile);
+ // are they equal????
+ Assert.Equal(expected, register.TenderValue);
+ }
+
+ #region Exception Tests
+ [Fact]
+ public void CashRegisterThrowOverflowExceptionGivenInputLargerThanLargestDecimalValue()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1m, "testMoney", "testMonies") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+ Assert.Throws(() => register.Tender(OverflowFile));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsFileNotFoundExceptionGivenEmptyOrNullPath()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1m, "testMoney", "testMonies") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+ Assert.Throws(() => register.Tender(""));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsFileNotFoundExceptionGivenNullPath()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1m, "testMoney", "testMonies") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+ Assert.Throws(() => register.Tender(null));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsFormatExceptionWhenEmptyFileIsProvided()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1m, "testMoney", "testMonies") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+ Assert.Throws(() => register.Tender(EmptyFile));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsFormatExceptionWhenEmptyLineFoundInFileProvided()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1m, "testMoney", "testMonies") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+ Assert.Throws(() => register.Tender(EmptyLineFile));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsNotEnoughTenderExceptionWhenTenderIsLessThanPrice()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1m, "testMoney", "testMonies") });
+ CashRegister register = new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object);
+ Assert.Throws(() => register.Tender(NotEnoughTenderFile));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsInvalidCurrencyExceptionWhenNoDenominationsFound()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List());
+ Assert.Throws(() => new POSCashRegister(currencyMock.Object, tenderStrategyMock.Object));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsNullReferenceExceptionWhenAttemptingToRegisterNullCurrency()
+ {
+ Assert.Throws(() => new POSCashRegister(null, tenderStrategyMock.Object));
+ }
+
+ [Fact]
+ public void CashRegisterThrowsNullReferenceExceptionWhenAttemptingToRegisterNullTenderStrategy()
+ {
+ Assert.Throws(() => new POSCashRegister(currencyMock.Object, null));
+ }
+
+ #endregion Exception Tests
+ }
+}
\ No newline at end of file
diff --git a/CashRegisterTests/CashRegisterTests.csproj b/CashRegisterTests/CashRegisterTests.csproj
new file mode 100644
index 00000000..134918e5
--- /dev/null
+++ b/CashRegisterTests/CashRegisterTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netcoreapp3.0
+
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/CashRegisterTests/CurrencyTests.cs b/CashRegisterTests/CurrencyTests.cs
new file mode 100644
index 00000000..b34da92b
--- /dev/null
+++ b/CashRegisterTests/CurrencyTests.cs
@@ -0,0 +1,179 @@
+using CashRegisterConsumer;
+using System.Collections.Generic;
+using Xunit;
+
+namespace CurrencyTests
+{
+ public class CurrencyTests
+ {
+ #region Setup
+
+ public CurrencyTests()
+ {
+ }
+
+ #endregion Setup
+
+ [Fact]
+ public void CurrencyClearClearsMoneyCountForBills()
+ {
+ Currency currency = new CurrencyTestSortReverseCurrency();
+
+ //now that we know we have a count in each of our bills... we will clear the currency and test.
+ currency.Clear();
+
+ foreach (Money bill in currency.Bills)
+ {
+ Assert.True(bill.Count == 0);
+ }
+ }
+
+ [Fact]
+ public void CurrencyClearClearsMoneyCountForCoins()
+ {
+ Currency currency = new CurrencyTestSortReverseCurrency();
+
+ //now that we know we have a count in each of our coins... we will clear the currency and test.
+ currency.Clear();
+
+ foreach (Money coin in currency.Bills)
+ {
+ Assert.True(coin.Count == 0);
+ }
+ }
+
+ [Fact]
+ public void CurrencyAllDenominationsReturnsConcatForBillsAndCoins()
+ {
+ // this also effectively tests the "sort/reverse" functionality of the InitializeCurrency method
+ // so creating a new method for that would be redundent (not necessarily bad though)
+ Currency currency = new CurrencyTestPluralNameCurrencyNoMoney();
+
+ for (int i = 0; i < currency.Bills.Count - 1; i++)
+ {
+ Assert.Equal(currency.Bills[i], currency.AllDenominations[i]);
+ }
+
+ for (int i = 0; i < currency.Coins.Count - 1; i++)
+ {
+ Assert.Equal(currency.Coins[i], currency.AllDenominations[i + currency.Bills.Count]); // coins should start after bills due to the sort/reverse (denomination based)
+ }
+ }
+ }
+
+ #region CurrecyTestClass
+
+ public class CurrencyTestSortReverseCurrency : Currency
+ {
+ public CurrencyTestSortReverseCurrency() : base()
+ {
+ }
+
+ protected override void InitializeCurrency()
+ {
+ this._bills = new List() {
+ new Bill(5, "five", "fives"),
+ new Bill(100000, "one hundred thousand", "one hundred thousands"),
+ new Bill(50, "fifty", "fifties"),
+ new Bill(1000, "thousand", "thousands"),
+ new Bill(500, "five hundred", "five hundreds"),
+ new Bill(5000, "five thousand", "five thousands"),
+ new Bill(100, "hundred", "hundreds"),
+ new Bill(20, "twenty", "twenties"),
+ new Bill(10000, "ten thousand", "ten thousands"),
+ new Bill(10, "ten", "tens"),
+ new Bill(1, "dollar", "dollars")
+ };
+
+ this._coins = new List() {
+ new Coin(.05m, "nickel", "nickels"),
+ new Coin(.01m, "penny","pennies"),
+ new Coin(.25m, "quarter", "quarters"),
+ new Coin(.10m, "dime", "dimes")
+ };
+ }
+ }
+
+ public class CurrencyTestSingularNameCurrency : Currency
+ {
+ protected override void InitializeCurrency()
+ {
+ this._bills = new List() {
+ new Bill(100000, "one hundred thousand", "one hundred thousands", 1),
+ new Bill(10000, "ten thousand", "ten thousands", 1),
+ new Bill(5000, "five thousand", "five thousands", 1),
+ new Bill(1000, "thousand", "thousands", 1),
+ new Bill(500, "five hundred", "five hundreds", 1),
+ new Bill(100, "hundred", "hundreds", 1),
+ new Bill(50, "fifty", "fifties", 1),
+ new Bill(20, "twenty", "twenties", 1),
+ new Bill(10, "ten", "tens", 1),
+ new Bill(5, "five", "fives", 1),
+ new Bill(1, "dollar", "dollars", 1)
+ };
+
+ this._coins = new List() {
+ new Coin(.25m, "quarter", "quarters", 1),
+ new Coin(.10m, "dime", "dimes", 1),
+ new Coin(.05m, "nickel", "nickels", 1),
+ new Coin(.01m, "penny","pennies", 1)
+ };
+ }
+ }
+
+ public class CurrencyTestPluralNameCurrency : Currency
+ {
+ protected override void InitializeCurrency()
+ {
+ this._bills = new List() {
+ new Bill(100000, "one hundred thousand", "one hundred thousands", 50),
+ new Bill(10000, "ten thousand", "ten thousands", 2),
+ new Bill(5000, "five thousand", "five thousands", 2),
+ new Bill(1000, "thousand", "thousands", 4),
+ new Bill(500, "five hundred", "five hundreds", 2),
+ new Bill(100, "hundred", "hundreds", 600),
+ new Bill(50, "fifty", "fifties", 56),
+ new Bill(20, "twenty", "twenties", 2),
+ new Bill(10, "ten", "tens", 4),
+ new Bill(5, "five", "fives", 3),
+ new Bill(1, "dollar", "dollars", 1245)
+ };
+
+ this._coins = new List() {
+ new Coin(.25m, "quarter", "quarters", 500),
+ new Coin(.10m, "dime", "dimes", 5),
+ new Coin(.05m, "nickel", "nickels", 40),
+ new Coin(.01m, "penny","pennies", 100)
+ };
+ }
+ }
+
+ public class CurrencyTestPluralNameCurrencyNoMoney : Currency
+ {
+ protected override void InitializeCurrency()
+ {
+ this._bills = new List() {
+ new Bill(100000, "one hundred thousand", "one hundred thousands"),
+ new Bill(10000, "ten thousand", "ten thousands"),
+ new Bill(5000, "five thousand", "five thousands"),
+ new Bill(1000, "thousand", "thousands"),
+ new Bill(500, "five hundred", "five hundreds"),
+ new Bill(100, "hundred", "hundreds"),
+ new Bill(50, "fifty", "fifties"),
+ new Bill(20, "twenty", "twenties"),
+ new Bill(10, "ten", "tens"),
+ new Bill(5, "five", "fives"),
+ new Bill(1, "dollar", "dollars")
+ };
+
+ this._coins = new List() {
+ new Coin(.25m, "quarter", "quarters"),
+ new Coin(.10m, "dime", "dimes"),
+ new Coin(.05m, "nickel", "nickels"),
+ new Coin(.01m, "penny","pennies")
+ };
+ }
+ }
+
+ #endregion CurrecyTestClass
+}
\ No newline at end of file
diff --git a/CashRegisterTests/MoneyTests.cs b/CashRegisterTests/MoneyTests.cs
new file mode 100644
index 00000000..786835e1
--- /dev/null
+++ b/CashRegisterTests/MoneyTests.cs
@@ -0,0 +1,131 @@
+using CashRegisterConsumer;
+using Moq;
+using Xunit;
+
+namespace MoneyTests
+{
+ public class MoneyTests
+ {
+ #region Setup
+
+ public MoneyTests()
+ {
+ }
+
+ #endregion Setup
+
+ [Fact]
+ public void MoneyConstructionSetsDenominationAccurately()
+ {
+ decimal expected = 0.01m;
+ Money money = new MoneyTestMoney(0.01m, "test", "tests", 3);
+
+ Assert.Equal(expected, money.Denomination);
+ }
+
+ [Fact]
+ public void MoneyConstructionSetsSingularNameAccurately()
+ {
+ string expected = "test";
+ Money money = new MoneyTestMoney(0.01m, "test", "tests", 1);
+
+ Assert.Equal(expected, money.Name);
+ }
+
+ [Fact]
+ public void MoneyConstructionSetsPluralNameAccurately()
+ {
+ string expected = "tests";
+ Money money = new MoneyTestMoney(0.01m, "test", "tests");
+
+ Assert.Equal(expected, money.Name);
+ }
+
+ [Fact]
+ public void MoneyConstructionSetsPluralNameWithZeroCount()
+ {
+ string expected = "tests";
+ Money money = new MoneyTestMoney(0.01m, "test", "tests");
+
+ Assert.Equal(expected, money.Name);
+ }
+
+ [Fact]
+ public void MoneyAddsCorrectAmount()
+ {
+ Money money = new MoneyTestMoney(1, "test", "tests");
+ Assert.Equal(0, money.Count);
+
+ money.Add(1);
+ Assert.Equal(1, money.Count);
+
+ money.Add(-1);
+ Assert.Equal(0, money.Count);
+ }
+
+ [Fact]
+ public void MoneySubtractsCorrectAmount()
+ {
+ Money money = new MoneyTestMoney(1, "test", "tests", 5);
+ Assert.Equal(5, money.Count);
+
+ money.Subtract(1);
+ Assert.Equal(4, money.Count);
+
+ money.Subtract(-1);
+ Assert.Equal(5, money.Count);
+ }
+
+ [Fact]
+ public void MoneyClearsCount()
+ {
+ Money money = new MoneyTestMoney(1, "test", "tests", 5);
+ Assert.Equal(5, money.Count);
+
+ money.Clear();
+ Assert.Equal(0, money.Count);
+ }
+
+ [Fact]
+ public void MoneyCompareToReturnsZeroWhenEqual()
+ {
+ Mock moneyToCompare = new Mock(1m, "compare", "compares");
+ Mock moneyToCompareTo = new Mock(1m, "compare", "compares");
+
+ Assert.Equal(0, moneyToCompare.Object.CompareTo(moneyToCompareTo.Object));
+ }
+
+ [Fact]
+ public void MoneyCompareToReturnsOneWhenMoreThan()
+ {
+ Mock moneyToCompare = new Mock(1m, "compare", "compares");
+ Mock moneyToCompareTo = new Mock(0.50m, "compare", "compares");
+
+ Assert.Equal(1, moneyToCompare.Object.CompareTo(moneyToCompareTo.Object));
+ }
+
+ [Fact]
+ public void MoneyCompareToReturnsMinusOneWhenLessThan()
+ {
+ Mock moneyToCompare = new Mock(0.5m, "compare", "compares");
+ Mock moneyToCompareTo = new Mock(1m, "compare", "compares");
+
+ Assert.Equal(-1, moneyToCompare.Object.CompareTo(moneyToCompareTo.Object));
+ }
+ }
+
+ #region MoneyTestClass
+
+ public class MoneyTestMoney : Money
+ {
+ public MoneyTestMoney(decimal denomination, string singleName, string pluralName) : base(denomination, singleName, pluralName)
+ {
+ }
+
+ public MoneyTestMoney(decimal denomination, string singleName, string pluralName, int count) : base(denomination, singleName, pluralName, count)
+ {
+ }
+ }
+
+ #endregion MoneyTestClass
+}
\ No newline at end of file
diff --git a/CashRegisterTests/Random3StrategyTests.cs b/CashRegisterTests/Random3StrategyTests.cs
new file mode 100644
index 00000000..ab62bbce
--- /dev/null
+++ b/CashRegisterTests/Random3StrategyTests.cs
@@ -0,0 +1,244 @@
+using CashRegisterConsumer;
+using Moq;
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace TenderStrategyTests
+{
+ public class Random3StrategyTests
+ {
+ #region Setup
+
+ private readonly Mock mockCurrency;
+
+ public Random3StrategyTests()
+ {
+ mockCurrency = new Mock();
+ }
+
+ #endregion Setup
+
+ [Fact]
+ public void Random3TenderStrategyCalculatesChangeCorrectlyBasedOnPriceAndTenderDifference()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List()
+ { new Bill(10, "ten", "tens"),
+ new Bill(5, "five", "fives"),
+ new Bill(1, "dollar", "dollars"),
+ new Coin(.10m,"dime","dimes"),
+ new Coin(.05m,"nickel","nickels"),
+ new Coin(.01m,"penny","pennies")
+ });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ decimal expected = 3001.50m;
+ decimal actual = 0.0m;
+ var result = tenderStrategy.Calculate(mockCurrency.Object, 123.03m, 3124.53m); // 3001.50 in change
+ foreach (Money money in mockCurrency.Object.AllDenominations)
+ {
+ actual += (money.Count * money.Denomination);
+ }
+ Assert.Equal(expected, actual);
+
+ ClearCurrency(result);
+ expected = 0.0m;
+ actual = 0.0m;
+
+ result = tenderStrategy.Calculate(mockCurrency.Object, 1.03m, 1.03m); // 0 in change
+ foreach (Money money in mockCurrency.Object.AllDenominations)
+ {
+ actual += (money.Count * money.Denomination);
+ }
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Random3CalculatesExactTenderCorrectly()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List()
+ { new Bill(10, "ten", "tens"),
+ new Bill(5, "five", "fives"),
+ new Bill(1, "dollar", "dollars"),
+ new Coin(.10m,"dime","dimes"),
+ new Coin(.05m,"nickel","nickels"),
+ new Coin(.01m,"penny","pennies")
+ });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ decimal expected = 0.0m;
+ decimal actual = 0.0m;
+ var result = tenderStrategy.Calculate(mockCurrency.Object, 1.03m, 1.03m); // 0 in change
+ foreach (Money money in mockCurrency.Object.AllDenominations)
+ {
+ actual += (money.Count * money.Denomination);
+ }
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Random3CalculatesZeroTenderWhenTenderIsLessThanPrice()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List()
+ { new Bill(10, "ten", "tens"),
+ new Bill(5, "five", "fives"),
+ new Bill(1, "dollar", "dollars"),
+ new Coin(.10m,"dime","dimes"),
+ new Coin(.05m,"nickel","nickels"),
+ new Coin(.01m,"penny","pennies")
+ });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ decimal expected = 0.0m;
+ decimal actual = 0.0m;
+ var result = tenderStrategy.Calculate(mockCurrency.Object, 10.03m, 1.03m); // 0 in change
+ foreach (Money money in result.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ actual += (money.Count * money.Denomination);
+ }
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Random3TenderStrategySubtractsCorrectlyFromThePriceBasedOnNonDecimalDenominationsAndRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "dollar", "dollars") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1.33m, 3.33m);
+
+ // test that our "1 dollar bill" is added 2 times during the process for standard strategy for change
+ // (meaning that the price is recuded each time accordingly)
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(2, money.Count); // price - tender = 2 dollars in change
+ }
+ }
+
+ [Fact]
+ public void Random3TenderStrategySubtractsCorrectlyFromThePriceBasedOnDecimalDenominationsAndRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(.01m, "penny", "pennies") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1.33m, 1.43m);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(10, money.Count); // price - tender = 10 pennies in change
+ }
+
+
+ ClearCurrency(actual);
+ actual = tenderStrategy.Calculate(mockCurrency.Object, 3.00m, 5.00m);
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(200, money.Count); // price - tender = 200 pennies in change
+ }
+
+ ClearCurrency(actual);
+ actual = tenderStrategy.Calculate(mockCurrency.Object, 6.00m, 7.00m);
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(100, money.Count); // price - tender = 100 pennies in change
+ }
+ }
+
+ private void ClearCurrency(ICurrency currency)
+ {
+ foreach (Money money in currency.AllDenominations)
+ {
+ money.Clear();
+ }
+ }
+
+ [Fact]
+ public void Random3TenderStrategySubtractsCorrectlyFromThePriceBasedOnOddDecimalDenominationsAndRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(.25m, "quarter", "quarters") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1.33m, 2.33m);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(4, money.Count); // price - tender = 4 quarters in change
+ }
+
+ ClearCurrency(actual);
+ actual = tenderStrategy.Calculate(mockCurrency.Object, 3.00m, 5.00m);
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(8, money.Count); // price - tender = 8 quarters in change
+ }
+
+ ClearCurrency(actual);
+ actual = tenderStrategy.Calculate(mockCurrency.Object, 6.00m, 7.00m);
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(4, money.Count); // price - tender = 4 quarters in change
+ }
+ }
+
+ [Fact]
+ public void Random3TenderStrategyReturnsCurrentyWithNoMoneyValuesAndNonRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "test", "tests") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 0, 0);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(0, money.Count); // due to the "ToString" override being used, we have to test the counts.
+ } // Consider refactoring ToString. Hard testing indicates a design flaw.
+ }
+
+ [Fact]
+ public void Random3TenderStrategySubtractsCorrectlyFromThePriceBasedOnNonDecimalDenominationsAndNonRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "dollar", "dollars") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1, 3);
+
+ // test that our "1 dollar bill" is added 2 times during the process for standard strategy for change
+ // (meaning that the price is recuded each time accordingly)
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(2, money.Count); // price - tender = 2 dollars in change
+ }
+ }
+
+ [Fact]
+ public void Random3TenderStrategySubtractsCorrectlyFromThePriceBasedOnDecimalDenominationsAndNonRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(.01m, "penny", "pennies") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1, 1.10m);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(10, money.Count); // price - tender = 10 pennies in change
+ }
+ }
+
+ [Fact]
+ public void Random3TenderStrategySubtractsCorrectlyFromThePriceBasedOnOddDecimalDenominationsAndNonRandomReturn()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(.25m, "quarter", "quarters") });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1, 2.00m);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(4, money.Count); // price - tender = 4 quarters in change
+ }
+ }
+
+
+ #region Exception Testing
+
+ [Fact]
+ public void Random3TenderStrategyCalculateThrowsInvalidCurrencyExceptionWhenNoDenominationsFound()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { });
+ ITenderStrategy tenderStrategy = new Random3Strategy();
+ Assert.Throws(() => tenderStrategy.Calculate(mockCurrency.Object, 0, 0));
+ }
+
+ #endregion Exception Testing
+ }
+}
\ No newline at end of file
diff --git a/CashRegisterTests/StandardTenderStrategyTests.cs b/CashRegisterTests/StandardTenderStrategyTests.cs
new file mode 100644
index 00000000..e35dd0e3
--- /dev/null
+++ b/CashRegisterTests/StandardTenderStrategyTests.cs
@@ -0,0 +1,109 @@
+using CashRegisterConsumer;
+using Moq;
+using System.Collections.Generic;
+using Xunit;
+
+namespace TenderStrategyTests
+{
+ public class StandardTenderStrategyTests
+ {
+ #region Setup
+
+ private readonly Mock mockCurrency;
+
+ public StandardTenderStrategyTests()
+ {
+ mockCurrency = new Mock();
+ }
+
+ #endregion Setup
+
+ [Fact]
+ public void StandardCalculatesZeroTenderWhenTenderIsLessThanPrice()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List()
+ { new Bill(10, "ten", "tens"),
+ new Bill(5, "five", "fives"),
+ new Bill(1, "dollar", "dollars"),
+ new Coin(.10m,"dime","dimes"),
+ new Coin(.05m,"nickel","nickels"),
+ new Coin(.01m,"penny","pennies")
+ });
+ ITenderStrategy tenderStrategy = new StandardTenderStrategy();
+ decimal expected = 0.0m;
+ decimal actual = 0.0m;
+ var result = tenderStrategy.Calculate(mockCurrency.Object, 10.03m, 1.03m); // 0 in change
+ foreach (Money money in mockCurrency.Object.AllDenominations)
+ {
+ actual += (money.Count * money.Denomination);
+ }
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void StandardTenderStrategyReturnsCurrentyWithNoMoneyValues()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "test", "tests") });
+ ITenderStrategy tenderStrategy = new StandardTenderStrategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 0, 0);
+
+ foreach (Money money in actual.AllDenominations)// mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(0, money.Count); // due to the "ToString" override being used, we have to test the counts.
+ } // Consider refactoring ToString. Hard testing indicates a design flaw.
+ }
+
+ [Fact]
+ public void StandardTenderStrategySubtractsCorrectlyFromThePriceBasedOnNonDecimalDenominations()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "dollar", "dollars") });
+ ITenderStrategy tenderStrategy = new StandardTenderStrategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1, 3);
+
+ // test that our "1 dollar bill" is added 2 times during the process for standard strategy for change
+ // (meaning that the price is recuded each time accordingly)
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(2, money.Count); // price - tender = 2 dollars in change
+ }
+ }
+
+ [Fact]
+ public void StandardTenderStrategySubtractsCorrectlyFromThePriceBasedOnDecimalDenominations()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(.01m, "penny", "pennies") });
+ ITenderStrategy tenderStrategy = new StandardTenderStrategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1, 1.10m);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(10, money.Count); // price - tender = 10 pennies in change
+ }
+ }
+
+ [Fact]
+ public void StandardTenderStrategySubtractsCorrectlyFromThePriceBasedOnOddDecimalDenominations()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { new Bill(.25m, "quarter", "quarters") });
+ ITenderStrategy tenderStrategy = new StandardTenderStrategy();
+ var actual = tenderStrategy.Calculate(mockCurrency.Object, 1, 2.00m);
+
+ foreach (Money money in actual.AllDenominations) // mockCurrency.Object.AllDenominations)
+ {
+ Assert.Equal(4, money.Count); // price - tender = 4 quarters in change
+ }
+ }
+
+ #region Exception Testing
+
+ [Fact]
+ public void StandardTenderStrategyCalculateThrowsInvalidCurrencyExceptionWhenNoDenominationsFound()
+ {
+ mockCurrency.Setup(p => p.AllDenominations).Returns(new List() { });
+ ITenderStrategy tenderStrategy = new StandardTenderStrategy();
+ Assert.Throws(() => tenderStrategy.Calculate(mockCurrency.Object, 0, 0));
+ }
+
+ #endregion Exception Testing
+ }
+}
\ No newline at end of file
diff --git a/CashRegisterTests/TenderStrategyTests.cs b/CashRegisterTests/TenderStrategyTests.cs
new file mode 100644
index 00000000..62914ce8
--- /dev/null
+++ b/CashRegisterTests/TenderStrategyTests.cs
@@ -0,0 +1,87 @@
+using CashRegisterConsumer;
+using Moq;
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace TenderStrategyTests
+{
+ public class TenderStrategyTests
+ {
+ #region Setup
+
+ private readonly Mock currencyMock;
+
+ public TenderStrategyTests()
+ {
+ currencyMock = new Mock();
+ }
+
+ #endregion Setup
+
+ [Fact]
+ public void TenderStrategyDisplayReturnsProperlyFormattedStringValueForSingularCount()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "dollar", "dollars", 1) });
+ string expected = "1 dollar\n";
+
+ TenderStrategy tenderStrategy = new TenderStrategyTestMock();
+ var actual = tenderStrategy.Display(currencyMock.Object);
+
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void TenderStrategyDisplayReturnsProperlyFormattedStringValueForMultipleCount()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "dollar", "dollars", 2) });
+ string expected = "2 dollars\n";
+
+ TenderStrategy tenderStrategy = new TenderStrategyTestMock();
+ var actual = tenderStrategy.Display(currencyMock.Object);
+
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void TenderStrategyDisplayReturnsProperlyFormattedStringValueForZeroCount()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() { new Bill(1, "dollar", "dollars", 0) });
+ string expected = "No Change Due\n";
+
+ TenderStrategy tenderStrategy = new TenderStrategyTestMock();
+ var actual = tenderStrategy.Display(currencyMock.Object);
+
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void TenderStrategyDisplayReturnsProperStringValueForMultipleDenominations()
+ {
+ currencyMock.Setup(p => p.AllDenominations).Returns(new List() {
+ new Bill(5, "five dollar", "five dollars", 10),
+ new Bill(1, "dollar", "dollars", 1),
+ new Coin(.01m, "dime", "dimes",3),
+ new Coin(.01m,"penny","pennies",1)
+ });
+ string expected = "10 five dollars,1 dollar,3 dimes,1 penny\n";
+
+ TenderStrategy tenderStrategy = new TenderStrategyTestMock();
+ var actual = tenderStrategy.Display(currencyMock.Object);
+
+ Assert.Equal(expected, actual);
+ }
+ }
+
+ #region MockTenderStrategy Abstract
+
+ internal class TenderStrategyTestMock : TenderStrategy
+ {
+ public override ICurrency Calculate(ICurrency currency, decimal price, decimal tender)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion MockTenderStrategy Abstract
+}
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/EmptyFile.txt b/CashRegisterTests/Test Input Files/EmptyFile.txt
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/EmptyFile.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/EmptyLineFile.txt b/CashRegisterTests/Test Input Files/EmptyLineFile.txt
new file mode 100644
index 00000000..c6473c1e
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/EmptyLineFile.txt
@@ -0,0 +1,2 @@
+
+5.55,6.66
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/SingleTransactionTestFile.txt b/CashRegisterTests/Test Input Files/SingleTransactionTestFile.txt
new file mode 100644
index 00000000..bfd78cb5
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/SingleTransactionTestFile.txt
@@ -0,0 +1 @@
+2.50, 5.00
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/TenderLessThanPrice.txt b/CashRegisterTests/Test Input Files/TenderLessThanPrice.txt
new file mode 100644
index 00000000..6c37fe19
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/TenderLessThanPrice.txt
@@ -0,0 +1 @@
+6.00, 4.50
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/ValueTests/OverflowTransactionTestFile.txt b/CashRegisterTests/Test Input Files/ValueTests/OverflowTransactionTestFile.txt
new file mode 100644
index 00000000..1ee76b03
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/ValueTests/OverflowTransactionTestFile.txt
@@ -0,0 +1 @@
+7922816251426433759354395033511, 79228162514264337593543950335111
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/ValueTests/PriceValueTestFile.txt b/CashRegisterTests/Test Input Files/ValueTests/PriceValueTestFile.txt
new file mode 100644
index 00000000..cea39776
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/ValueTests/PriceValueTestFile.txt
@@ -0,0 +1 @@
+ 2.45,110.98
\ No newline at end of file
diff --git a/CashRegisterTests/Test Input Files/ValueTests/TenderValueTestFile.txt b/CashRegisterTests/Test Input Files/ValueTests/TenderValueTestFile.txt
new file mode 100644
index 00000000..cea39776
--- /dev/null
+++ b/CashRegisterTests/Test Input Files/ValueTests/TenderValueTestFile.txt
@@ -0,0 +1 @@
+ 2.45,110.98
\ No newline at end of file