diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5d45982c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +bin/ +obj/ + +TestResults/ \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/CashDrawer.App.Tests.csproj b/CreativeCashDrawer/CashDrawer.App.Tests/CashDrawer.App.Tests.csproj new file mode 100644 index 00000000..3c6aee7d --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/CashDrawer.App.Tests.csproj @@ -0,0 +1,32 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderData.txt b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderData.txt new file mode 100644 index 00000000..2ff39cc0 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderData.txt @@ -0,0 +1,3 @@ +1.00,2.00 +3.00,4.00 +5.00,6.00 \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderTests.cs new file mode 100644 index 00000000..38a4dd33 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderTests.cs @@ -0,0 +1,39 @@ +using CashDrawer.App.FileReaders; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.App.Tests.FileReaderTests +{ + [TestClass] + public class InputFileReaderTests + { + + [TestMethod] + public void file_reader_reads_input_from_a_file() + { + var filename = @"FileReaderTests\InputFileReaderData.txt"; + + var reader = new InputFileReader(filename, new LineParser()); + + Assert.IsTrue(reader.HaveMore); + var line = reader.Next(); + Assert.IsFalse(line.HasError); + Assert.AreEqual(1.00m, line.Due); + Assert.AreEqual(2.00m, line.Paid); + + Assert.IsTrue(reader.HaveMore); + line = reader.Next(); + Assert.IsFalse(line.HasError); + Assert.AreEqual(3.00m, line.Due); + Assert.AreEqual(4.00m, line.Paid); + + Assert.IsTrue(reader.HaveMore); + line = reader.Next(); + Assert.IsFalse(line.HasError); + Assert.AreEqual(5.00m, line.Due); + Assert.AreEqual(6.00m, line.Paid); + + Assert.IsFalse(reader.HaveMore); + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/LineParserTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/LineParserTests.cs new file mode 100644 index 00000000..874879bf --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/LineParserTests.cs @@ -0,0 +1,116 @@ +using CashDrawer.App.FileReaders; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.App.Tests.FileReaderTests +{ + + [TestClass] + public class LineParserTests + { + + [TestMethod] + public void parser_can_parse_line_with_good_due_and_paid_values() + { + var line = "10.00,12.00"; + + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsFalse(result.HasError); + Assert.AreEqual(10.00m, result.Due); + Assert.AreEqual(12.00m, result.Paid); + } + + + + [TestMethod] + public void parser_ignores_white_space() + { + var line = " 10.00 , 12.00 "; + + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsFalse(result.HasError); + Assert.AreEqual(10.00m, result.Due); + Assert.AreEqual(12.00m, result.Paid); + } + + + + [TestMethod] + [DataRow("1.00")] + [DataRow("1.00,")] + [DataRow("1.00, 2.00, 3.00")] + [DataRow("")] + [DataRow(",")] + public void parser_returns_error_if_not_two_amounts_on_line(string line) + { + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsTrue(result.HasError); + Assert.AreEqual("Invalid line. Expected format ,", result.Error); + } + + + + [TestMethod] + public void parser_returns_error_if_invalid_due_amount() + { + var line = "X.XX, 1.00"; + + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsTrue(result.HasError); + Assert.AreEqual("Invalid amount 'X.XX'", result.Error); + } + + + + [TestMethod] + public void parser_returns_error_if_invalid_paid_amount() + { + var line = "1.00, X.XX"; + + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsTrue(result.HasError); + Assert.AreEqual("Invalid amount 'X.XX'", result.Error); + } + + + + [TestMethod] + [DataRow("1 , 0.00", "Invalid amount '1'. Amount must have 2 digits after the decimal.")] + [DataRow("1. , 0.00", "Invalid amount '1.'. Amount must have 2 digits after the decimal.")] + [DataRow("1.1 , 0.00", "Invalid amount '1.1'. Amount must have 2 digits after the decimal.")] + public void parser_returns_error_if_due_amount_does_not_have_exactly_two_digits_after_decimal(string line, string expectedError) + { + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsTrue(result.HasError); + Assert.AreEqual(expectedError, result.Error); + } + + + + [TestMethod] + [DataRow("0.00, 1 ", "Invalid amount '1'. Amount must have 2 digits after the decimal.")] + [DataRow("0.00, 1. ", "Invalid amount '1.'. Amount must have 2 digits after the decimal.")] + [DataRow("0.00, 1.1 ", "Invalid amount '1.1'. Amount must have 2 digits after the decimal.")] + public void parser_returns_error_if_paid_amount_does_not_have_exactly_two_digits_after_decimal(string line, string expectedError) + { + var parser = new LineParser(); + var result = parser.Parse(line); + + Assert.IsTrue(result.HasError); + Assert.AreEqual(expectedError, result.Error); + } + + } + +} diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/HumanizerTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/HumanizerTests.cs new file mode 100644 index 00000000..d6b5aa2f --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/HumanizerTests.cs @@ -0,0 +1,84 @@ +using CashDrawer.App.FileWriters; +using CashDrawer.Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.App.Tests.FileWriterTests +{ + [TestClass] + public class HumanizerTests + { + + [TestMethod] + public void humanizer_converts_change_to_friendly_string() + { + + var change = new Change(5, 4, 3, 2, 1); + var humanizer = new Humanizer(); + var text = humanizer.Humanize(change); + + Assert.AreEqual("5 dollars, 4 quarters, 3 dimes, 2 nickles, 1 penny", text); + } + + + + [TestMethod] + public void humanizer_converts_uses_singular_nouns_when_change_values_are_1() + { + + var change = new Change(1, 1, 1, 1, 1); + var humanizer = new Humanizer(); + var text = humanizer.Humanize(change); + + Assert.AreEqual("1 dollar, 1 quarter, 1 dime, 1 nickle, 1 penny", text); + } + + + + [TestMethod] + public void humanizer_converts_uses_plural_nouns_when_change_values_are_greater_than_0() + { + + var change = new Change(2, 2, 2, 2, 2); + var humanizer = new Humanizer(); + var text = humanizer.Humanize(change); + + Assert.AreEqual("2 dollars, 2 quarters, 2 dimes, 2 nickles, 2 pennies", text); + } + + + + [TestMethod] + [DataRow(0, 1, 1, 1, 1, "1 quarter, 1 dime, 1 nickle, 1 penny")] + [DataRow(1, 0, 1, 1, 1, "1 dollar, 1 dime, 1 nickle, 1 penny")] + [DataRow(1, 1, 0, 1, 1, "1 dollar, 1 quarter, 1 nickle, 1 penny")] + [DataRow(1, 1, 1, 0, 1, "1 dollar, 1 quarter, 1 dime, 1 penny")] + [DataRow(1, 1, 1, 1, 0, "1 dollar, 1 quarter, 1 dime, 1 nickle")] + public void humanizer_does_not_display_change_amount_if_value_is_zero(int dollars, + int quarters, + int dimes, + int nickles, + int pennies, + string expectedText) + { + var change = new Change(dollars, quarters, dimes, nickles, pennies); + var humanizer = new Humanizer(); + var text = humanizer.Humanize(change); + + Assert.AreEqual(expectedText, text); + } + + + + [TestMethod] + public void humanizer_returns_message_if_no_change_due() + { + + var change = new Change(0, 0, 0, 0, 0); + var humanizer = new Humanizer(); + var text = humanizer.Humanize(change); + + Assert.AreEqual("no change due", text); + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/OutputFileWriterTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/OutputFileWriterTests.cs new file mode 100644 index 00000000..05221ed3 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/OutputFileWriterTests.cs @@ -0,0 +1,34 @@ +using CashDrawer.App.FileWriters; +using CashDrawer.Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; + +namespace CashDrawer.App.Tests.FileWriterTests +{ + [TestClass] + public class OutputFileWriterTests + { + + [TestMethod] + public void file_writer_writes_change_to_a_file() + { + var filename = "FileWriterTest.txt"; + + File.Delete(filename); + + var writer = new OutputFileWriter(filename, new Humanizer()); + + var change1 = new Change(0, 1, 2, 3, 4); + var change2 = new Change(5, 6, 7, 8, 9); + writer.Write(change1); + writer.Write(change2); + + var output = File.ReadAllLines(filename); + + Assert.AreEqual(2, output.Length); + Assert.AreEqual("1 quarter, 2 dimes, 3 nickles, 4 pennies", output[0]); + Assert.AreEqual("5 dollars, 6 quarters, 7 dimes, 8 nickles, 9 pennies", output[1]); + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/Helpers/DummyConsole.cs b/CreativeCashDrawer/CashDrawer.App.Tests/Helpers/DummyConsole.cs new file mode 100644 index 00000000..0bf94577 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/Helpers/DummyConsole.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; + +namespace CashDrawer.App.Tests.Helpers +{ + public sealed class DummyConsole : IDisposable + { + private readonly TextWriter _oldWriter; + private readonly StringWriter _newWriter; + + public string Text => _newWriter.ToString(); + + + public DummyConsole() + { + _newWriter = new StringWriter(); + _oldWriter = Console.Out; + Console.SetOut(_newWriter); + } + + + public void Dispose() + { + Console.SetOut(_oldWriter); + _newWriter.Dispose(); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Empty.txt b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Empty.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Empty.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Good.txt b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Good.txt new file mode 100644 index 00000000..b812326d --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Good.txt @@ -0,0 +1,2 @@ +2.12,3.00 +1.97,2.00 \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/RunnerTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/RunnerTests.cs new file mode 100644 index 00000000..d28a1c9d --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/RunnerTests.cs @@ -0,0 +1,72 @@ +using CashDrawer.App.Tests.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; + +namespace CashDrawer.App.Tests.MainTests +{ + [TestClass] + public class RunnerTests + { + + [TestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(3)] + public void runner_writes_error_to_console_if_arg_count_incorrect(int argCount) + { + using var console = new DummyConsole(); + + var args = new string[argCount]; + var runner = new Runner(); + runner.Run(args); + + Assert.IsTrue(console.Text.StartsWith("Invalid command."), console.Text); + } + + + + [TestMethod] + public void runner_writes_error_to_console_if_input_file_not_found() + { + using var console = new DummyConsole(); + + var args = new [] { "missing-file.txt", @"MainTests\out.txt" }; + var runner = new Runner(); + runner.Run(args); + + Assert.AreEqual("Input file not found." + Environment.NewLine, console.Text); + } + + + + [TestMethod] + public void runner_writes_error_to_console_if_input_file_is_empty() + { + using var console = new DummyConsole(); + + var args = new [] { @"MainTests\InputFile-Empty.txt", @"MainTests\out.txt" }; + var runner = new Runner(); + runner.Run(args); + + Assert.AreEqual("Input file is empty. Nothing to process." + Environment.NewLine, console.Text); + } + + + + [TestMethod] + public void runner_writes_results_to_output_file() + { + var outputFileName = @"MainTests\out.txt"; + var args = new[] { @"MainTests\InputFile-Good.txt", outputFileName }; + var runner = new Runner(); + runner.Run(args); + + var output = File.ReadAllLines(outputFileName); + + Assert.AreEqual(2, output.Length); + Assert.AreEqual("3 quarters, 1 dime, 3 pennies", output[0]); + Assert.AreEqual("3 pennies", output[1]); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.App/CashDrawer.App.csproj b/CreativeCashDrawer/CashDrawer.App/CashDrawer.App.csproj new file mode 100644 index 00000000..67c3d651 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/CashDrawer.App.csproj @@ -0,0 +1,19 @@ + + + + Exe + net5.0 + CashDrawer + + + + + + + + + PreserveNewest + + + + diff --git a/CreativeCashDrawer/CashDrawer.App/FileReaders/ILineParser.cs b/CreativeCashDrawer/CashDrawer.App/FileReaders/ILineParser.cs new file mode 100644 index 00000000..336063f9 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/FileReaders/ILineParser.cs @@ -0,0 +1,9 @@ +using CashDrawer.Core.Readers; + +namespace CashDrawer.App.FileReaders +{ + public interface ILineParser + { + ReadResult Parse(string line); + } +} \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App/FileReaders/InputFileReader.cs b/CreativeCashDrawer/CashDrawer.App/FileReaders/InputFileReader.cs new file mode 100644 index 00000000..d3955635 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/FileReaders/InputFileReader.cs @@ -0,0 +1,31 @@ +using CashDrawer.Core.Readers; +using System; +using System.IO; + +namespace CashDrawer.App.FileReaders +{ + public sealed class InputFileReader : IDisposable, IInputReader + { + private StreamReader _reader; + private readonly ILineParser _lineParser; + + public InputFileReader(string filename, ILineParser lineParser) + { + _reader = new StreamReader(filename); + _lineParser = lineParser; + } + + public void Dispose() + { + _reader.Dispose(); + } + + public bool HaveMore => !_reader.EndOfStream; + + public ReadResult Next() + { + var s = _reader.ReadLine(); + return _lineParser.Parse(s); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.App/FileReaders/LineParser.cs b/CreativeCashDrawer/CashDrawer.App/FileReaders/LineParser.cs new file mode 100644 index 00000000..09ad6e25 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/FileReaders/LineParser.cs @@ -0,0 +1,61 @@ +using CashDrawer.Core.Readers; +using System; + +namespace CashDrawer.App.FileReaders +{ + public class LineParser : ILineParser + { + public ReadResult Parse(string line) + { + var parts = line.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (parts.Length != 2) + { + return ReadResult.Failed("Invalid line. Expected format ,"); + } + + var dueResult = ParseAmount(parts[0]); + if(dueResult.Error != null) + { + return ReadResult.Failed(dueResult.Error); + } + + var paidResult = ParseAmount(parts[1]); + if (paidResult.Error != null) + { + return ReadResult.Failed(paidResult.Error); + } + + return ReadResult.Ok(dueResult.Amount, paidResult.Amount); + } + + + + private (decimal Amount, string Error) ParseAmount(string s) + { + if (decimal.TryParse(s, out var amount) == false) + { + return (0, $"Invalid amount '{s}'"); + } + + if (DigitsAfterDecimal(s) != 2) + { + return (0, $"Invalid amount '{s}'. Amount must have 2 digits after the decimal."); + } + + return (amount, null); + } + + + + private int DigitsAfterDecimal(string paidString) + { + var i = paidString.IndexOf('.'); + if(i == -1) + { + return 0; + } + return paidString.Length - i - 1; + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.App/FileWriters/Humanizer.cs b/CreativeCashDrawer/CashDrawer.App/FileWriters/Humanizer.cs new file mode 100644 index 00000000..8c4c4f36 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/FileWriters/Humanizer.cs @@ -0,0 +1,40 @@ +using System.Text; +using CashDrawer.Core; + +namespace CashDrawer.App.FileWriters +{ + public class Humanizer : IHumanizer + { + + public string Humanize(Change change) + { + var s = new StringBuilder(); + AddPart(s, change.Dollars, "dollar", "dollars"); + AddPart(s, change.Quarters, "quarter", "quarters"); + AddPart(s, change.Dimes, "dime", "dimes"); + AddPart(s, change.Nickles, "nickle", "nickles"); + AddPart(s, change.Pennies, "penny", "pennies"); + + if (s.Length == 0) return "no change due"; + return string.Join(", ", s); + } + + + private void AddPart(StringBuilder s, int count, string singular, string plural) + { + if (count == 0) return; + var text = GetText(count, singular, plural); + if (s.Length > 0) s.Append(", "); + s.Append(text); + } + + + private string GetText(int count, string singular, string plural) + { + return count == 1 ? + $"{count} {singular}" : + $"{count} {plural}"; + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.App/FileWriters/IHumanizer.cs b/CreativeCashDrawer/CashDrawer.App/FileWriters/IHumanizer.cs new file mode 100644 index 00000000..48024c1b --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/FileWriters/IHumanizer.cs @@ -0,0 +1,9 @@ +using CashDrawer.Core; + +namespace CashDrawer.App.FileWriters +{ + public interface IHumanizer + { + string Humanize(Change change); + } +} \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App/FileWriters/OutputFileWriter.cs b/CreativeCashDrawer/CashDrawer.App/FileWriters/OutputFileWriter.cs new file mode 100644 index 00000000..393cf168 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/FileWriters/OutputFileWriter.cs @@ -0,0 +1,33 @@ +using CashDrawer.Core; +using CashDrawer.Core.Writers; +using System; +using System.IO; + +namespace CashDrawer.App.FileWriters +{ + public class OutputFileWriter : IOutputWriter + { + private readonly string _filename; + private readonly IHumanizer _humanizer; + + + public OutputFileWriter(string filename, IHumanizer humanizer) + { + _filename = filename; + _humanizer = humanizer; + } + + + public void Write(Change change) + { + var line = _humanizer.Humanize(change); + File.AppendAllText(_filename, line + Environment.NewLine); + } + + + public void WriteError(string error) + { + File.AppendAllText(_filename, error); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.App/Program.cs b/CreativeCashDrawer/CashDrawer.App/Program.cs new file mode 100644 index 00000000..5f8a13d7 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/Program.cs @@ -0,0 +1,61 @@ +using CashDrawer.App.FileReaders; +using CashDrawer.App.FileWriters; +using CashDrawer.Core; +using CashDrawer.Core.ChangeCalculatorFactories; +using System; +using System.IO; + +namespace CashDrawer.App +{ + class Program + { + static void Main(string[] args) + { + var runner = new Runner(); + runner.Run(args); + } + } + + + public class Runner + { + public void Run(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine("Invalid command."); + Console.WriteLine("Usage.........: CashDrawer "); + Console.WriteLine("For example...: CashDrawer input.txt output.txt"); + return; + } + + if (!File.Exists(args[0])) + { + Console.WriteLine("Input file not found."); + return; + } + + try + { + File.Delete(args[1]); + + var inputFileReader = new InputFileReader(args[0], new LineParser()); + var outputFileWriter = new OutputFileWriter(args[1], new Humanizer()); + var changeCalculatorFactory = new ChangeCalculatorFactory(); + + if (inputFileReader.HaveMore == false) + { + Console.WriteLine("Input file is empty. Nothing to process."); + return; + } + + var processor = new ChangeProcessor(changeCalculatorFactory); + processor.Process(inputFileReader, outputFileWriter); + } + catch (Exception e) + { + Console.WriteLine("Error processing file. " + e.Message); + } + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.App/Properties/launchSettings.json b/CreativeCashDrawer/CashDrawer.App/Properties/launchSettings.json new file mode 100644 index 00000000..25b6cb9e --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "ChangeApplication": { + "commandName": "Project", + "commandLineArgs": "SampleData\\input-file.txt out.txt" + } + } +} \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.App/SampleData/input-file.txt b/CreativeCashDrawer/CashDrawer.App/SampleData/input-file.txt new file mode 100644 index 00000000..197cfb3e --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.App/SampleData/input-file.txt @@ -0,0 +1,3 @@ +2.12,3.00 +1.97,2.00 +3.33,5.00 \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/CashDrawer.Core.Tests.csproj b/CreativeCashDrawer/CashDrawer.Core.Tests/CashDrawer.Core.Tests.csproj new file mode 100644 index 00000000..45647c96 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/CashDrawer.Core.Tests.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculatorFactories/ChangeCalculatorFactoryTests.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculatorFactories/ChangeCalculatorFactoryTests.cs new file mode 100644 index 00000000..551f426e --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculatorFactories/ChangeCalculatorFactoryTests.cs @@ -0,0 +1,32 @@ +using CashDrawer.Core.ChangeCalculatorFactories; +using CashDrawer.Core.ChangeCalculators; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.Core.Tests.ChangeCalculatorFactories +{ + + [TestClass] + public class ChangeCalculatorFactoryTests + { + + [TestMethod] + public void factory_returns_standard_change_calculator_if_due_amount_is_not_divisible_by_3() + { + var factory = new ChangeCalculatorFactory(); + var calculator = factory.GetChangeCalculator(10); + + Assert.IsTrue(calculator.GetType() == typeof(StandardChangeCalculator)); + } + + + [TestMethod] + public void factory_returns_random_change_calculator_if_due_amount_is_divisible_by_3() + { + var factory = new ChangeCalculatorFactory(); + var calculator = factory.GetChangeCalculator(9); + + Assert.IsTrue(calculator.GetType() == typeof(RandomChangeCalculator)); + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculators/RandomChangeCalculatorTests.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculators/RandomChangeCalculatorTests.cs new file mode 100644 index 00000000..7b895fd4 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculators/RandomChangeCalculatorTests.cs @@ -0,0 +1,51 @@ +using CashDrawer.Core.ChangeCalculators; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace CashDrawer.Core.Tests.ChangeCalculators +{ + [TestClass] + public class RandomChangeCalculatorTests + { + + [TestMethod] + public void random_calculator_returns_correct_change() + { + var calculator = new RandomChangeCalculator(new Random()); + + for(var i=0; i < 10000; i++) + { + var change = calculator.GetChange(due: 0.03m, paid: 10.00m); + Assert.AreEqual(997, ToPennies(change)); + } + } + + + [TestMethod] + public void random_calculator_produces_random_result() + { + var random = new Random(100); // seed 100 produces sequence 9 0 6 7 5 + + var calculator = new RandomChangeCalculator(random); + var change = calculator.GetChange(due: 2.00m, paid: 12.00m); + + Assert.AreEqual(9, change.Dollars); + Assert.AreEqual(0, change.Quarters); + Assert.AreEqual(6, change.Dimes); + Assert.AreEqual(7, change.Nickles); + Assert.AreEqual(5, change.Pennies); + } + + + private int ToPennies(Change change) + { + return + change.Dollars * 100 + + change.Quarters * 25 + + change.Dimes * 10 + + change.Nickles * 5 + + change.Pennies; + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculators/StandardChangeCalculatorTests.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculators/StandardChangeCalculatorTests.cs new file mode 100644 index 00000000..6b190bfc --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeCalculators/StandardChangeCalculatorTests.cs @@ -0,0 +1,43 @@ +using CashDrawer.Core.ChangeCalculators; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.Core.Tests.ChangeCalculators +{ + [TestClass] + public class StandardChangeCalculatorTests + { + + [TestMethod] + [DataRow("0.00", "0.00", 0, 0, 0, 0, 0)] + [DataRow("1.00", "2.00", 1, 0, 0, 0, 0)] + [DataRow("0.75", "1.00", 0, 1, 0, 0, 0)] + [DataRow("0.90", "1.00", 0, 0, 1, 0, 0)] + [DataRow("0.95", "1.00", 0, 0, 0, 1, 0)] + [DataRow("0.99", "1.00", 0, 0, 0, 0, 1)] + [DataRow("0.01", "3.00", 2, 3, 2, 0, 4)] + [DataRow("2.12", "3.00", 0, 3, 1, 0, 3)] + [DataRow("1.97", "2.00", 0, 0, 0, 0, 3)] + public void standard_calculator_returns_correct_change(string dueString, + string paidString, + int expectedDollars, + int expectedQuarters, + int expectedDimes, + int expectedNickles, + int expectedPennies) + { + var due = decimal.Parse(dueString); + var paid = decimal.Parse(paidString); + + var calulator = new StandardChangeCalculator(); + var change = calulator.GetChange(due, paid); + + Assert.AreEqual(expectedDollars, change.Dollars); + Assert.AreEqual(expectedQuarters, change.Quarters); + Assert.AreEqual(expectedDimes, change.Dimes); + Assert.AreEqual(expectedNickles, change.Nickles); + Assert.AreEqual(expectedPennies, change.Pennies); + + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeProcessorTests.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeProcessorTests.cs new file mode 100644 index 00000000..4cab1616 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeProcessorTests.cs @@ -0,0 +1,77 @@ +using CashDrawer.Core.ChangeCalculatorFactories; +using CashDrawer.Core.Readers; +using CashDrawer.Core.Tests.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace CashDrawer.Core.Tests +{ + [TestClass] + public class ChangeProcessorTests + { + + [TestMethod] + public void change_processor_writes_change_to_output() + { + var factory = new Mock(); + var changeCalculator = new FakeChangeCalculator(); + var reader = new FakeReader(); + var writer = new FakeWriter(); + + changeCalculator.ReturnedChange = new Change(1, 0, 0, 0, 0); + + reader.Add(ReadResult.Ok(10, 11)); + reader.Add(ReadResult.Ok(10, 11)); + reader.Add(ReadResult.Ok(10, 11)); + + factory.Setup(x => x.GetChangeCalculator(10)).Returns(changeCalculator); + + var processor = new ChangeProcessor(factory.Object); + processor.Process(reader, writer); + + factory.VerifyAll(); + Assert.AreEqual(3, writer.WrittenChange.Count); + Assert.AreEqual(1, writer.WrittenChange[0].Dollars); + Assert.AreEqual(1, writer.WrittenChange[1].Dollars); + Assert.AreEqual(1, writer.WrittenChange[2].Dollars); + } + + + + [TestMethod] + public void change_processor_writes_reader_error_to_output() + { + var factory = new Mock(); + var reader = new FakeReader(); + var writer = new FakeWriter(); + + var readerError = ReadResult.Failed("this failed"); + reader.Add(readerError); + + var processor = new ChangeProcessor(factory.Object); + processor.Process(reader, writer); + + Assert.AreEqual(1, writer.WrittenErrors.Count); + Assert.AreEqual("this failed", writer.WrittenErrors[0]); + } + + + + [TestMethod] + public void change_processor_writes_underpayment_error_to_output() + { + var factory = new Mock(); + var reader = new FakeReader(); + var writer = new FakeWriter(); + + var underpayment = ReadResult.Ok(10, 0); + reader.Add(underpayment); + + var processor = new ChangeProcessor(factory.Object); + processor.Process(reader, writer); + + Assert.AreEqual(1, writer.WrittenErrors.Count); + Assert.AreEqual("Underpayment", writer.WrittenErrors[0]); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeTests.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeTests.cs new file mode 100644 index 00000000..f381111d --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/ChangeTests.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.Core.Tests +{ + [TestClass] + public class ChangeTests + { + [TestMethod] + public void can_construct_change() + { + var change = new Change(1, 2, 3, 4, 5); + Assert.AreEqual(1, change.Dollars); + Assert.AreEqual(2, change.Quarters); + Assert.AreEqual(3, change.Dimes); + Assert.AreEqual(4, change.Nickles); + Assert.AreEqual(5, change.Pennies); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeChangeCalculator.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeChangeCalculator.cs new file mode 100644 index 00000000..c353eea3 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeChangeCalculator.cs @@ -0,0 +1,15 @@ +using CashDrawer.Core.ChangeCalculators; + +namespace CashDrawer.Core.Tests.Helpers +{ + public class FakeChangeCalculator : IChangeCalculator + { + public Change ReturnedChange; + + + public Change GetChange(decimal due, decimal paid) + { + return ReturnedChange; + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeReader.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeReader.cs new file mode 100644 index 00000000..1cf75ee5 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeReader.cs @@ -0,0 +1,26 @@ +using CashDrawer.Core.Readers; +using System.Collections.Generic; + +namespace CashDrawer.Core.Tests.Helpers +{ + public class FakeReader : IInputReader + { + private List _results = new(); + private int _position = 0; + + + public void Add(ReadResult result) + { + _results.Add(result); + } + + + public bool HaveMore => _position < _results.Count; + + + public ReadResult Next() + { + return _results[_position++]; + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeWriter.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeWriter.cs new file mode 100644 index 00000000..16d37083 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/Helpers/FakeWriter.cs @@ -0,0 +1,22 @@ +using CashDrawer.Core.Writers; +using System.Collections.Generic; + +namespace CashDrawer.Core.Tests.Helpers +{ + public class FakeWriter : IOutputWriter + { + public List WrittenChange = new(); + public List WrittenErrors = new(); + + + public void Write(Change change) + { + WrittenChange.Add(change); + } + + public void WriteError(string error) + { + WrittenErrors.Add(error); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core.Tests/Readers/ReadResultTests.cs b/CreativeCashDrawer/CashDrawer.Core.Tests/Readers/ReadResultTests.cs new file mode 100644 index 00000000..9376d553 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core.Tests/Readers/ReadResultTests.cs @@ -0,0 +1,29 @@ +using CashDrawer.Core.Readers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CashDrawer.Core.Tests.Readers +{ + [TestClass] + public class ReadResultTests + { + + [TestMethod] + public void ok_returns_result_with_due_and_paid_values() + { + var result = ReadResult.Ok(10, 11); + Assert.IsFalse(result.HasError); + Assert.AreEqual(10, result.Due); + Assert.AreEqual(11, result.Paid); + } + + + [TestMethod] + public void failed_returns_result_with_error() + { + var result = ReadResult.Failed("this failed"); + Assert.IsTrue(result.HasError); + Assert.AreEqual("this failed", result.Error); + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/CashDrawer.Core.csproj b/CreativeCashDrawer/CashDrawer.Core/CashDrawer.Core.csproj new file mode 100644 index 00000000..f208d303 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/CashDrawer.Core.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/CreativeCashDrawer/CashDrawer.Core/Change.cs b/CreativeCashDrawer/CashDrawer.Core/Change.cs new file mode 100644 index 00000000..f6c2dd14 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/Change.cs @@ -0,0 +1,20 @@ +namespace CashDrawer.Core +{ + public class Change + { + public readonly int Dollars; + public readonly int Quarters; + public readonly int Dimes; + public readonly int Nickles; + public readonly int Pennies; + + public Change(int dollars, int quarters, int dimes, int nickles, int pennies) + { + Dollars = dollars; + Quarters = quarters; + Dimes = dimes; + Nickles = nickles; + Pennies = pennies; + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/ChangeCalculatorFactories/ChangeCalculatorFactory.cs b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculatorFactories/ChangeCalculatorFactory.cs new file mode 100644 index 00000000..465accb2 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculatorFactories/ChangeCalculatorFactory.cs @@ -0,0 +1,24 @@ +using CashDrawer.Core.ChangeCalculators; +using System; + +namespace CashDrawer.Core.ChangeCalculatorFactories +{ + public class ChangeCalculatorFactory : IChangeCalculatorFactory + { + private static Random _random = new Random(); + + private IChangeCalculator _standardChangeCalculator = new StandardChangeCalculator(); + private IChangeCalculator _randomChangeCalculator = new RandomChangeCalculator(_random); + + + public IChangeCalculator GetChangeCalculator(decimal due) + { + var pennies = due * 100; + if (pennies % 3 == 0) + { + return _randomChangeCalculator; + } + return _standardChangeCalculator; + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/ChangeCalculatorFactories/IChangeCalculatorFactory.cs b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculatorFactories/IChangeCalculatorFactory.cs new file mode 100644 index 00000000..eb7f3eb4 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculatorFactories/IChangeCalculatorFactory.cs @@ -0,0 +1,9 @@ +using CashDrawer.Core.ChangeCalculators; + +namespace CashDrawer.Core.ChangeCalculatorFactories +{ + public interface IChangeCalculatorFactory + { + IChangeCalculator GetChangeCalculator(decimal due); + } +} \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/IChangeCalculator.cs b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/IChangeCalculator.cs new file mode 100644 index 00000000..99d55b4e --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/IChangeCalculator.cs @@ -0,0 +1,7 @@ +namespace CashDrawer.Core.ChangeCalculators +{ + public interface IChangeCalculator + { + Change GetChange(decimal due, decimal paid); + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/RandomChangeCalculator.cs b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/RandomChangeCalculator.cs new file mode 100644 index 00000000..fe764937 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/RandomChangeCalculator.cs @@ -0,0 +1,53 @@ +using System; + +namespace CashDrawer.Core.ChangeCalculators +{ + public class RandomChangeCalculator : IChangeCalculator + { + private readonly Random _random; + + + public RandomChangeCalculator(Random random) + { + _random = random; + } + + + public Change GetChange(decimal due, decimal paid) + { + var duePennies = (int)((paid - due) * 100); + + var dollars = 0; + if (duePennies >= 100) + { + dollars = _random.Next(0, duePennies / 100); + duePennies -= dollars * 100; + } + + var quarters = 0; + if (duePennies > 25) + { + quarters = _random.Next(0, duePennies / 25); + duePennies -= quarters * 25; + } + + var dimes = 0; + if (duePennies > 10) + { + dimes = _random.Next(0, duePennies / 10); + duePennies -= dimes * 10; + } + + var nickles = 0; + if (duePennies > 5) + { + nickles = _random.Next(0, duePennies / 5); + duePennies -= nickles * 5; + } + + var pennies = duePennies; + + return new Change(dollars, quarters, dimes, nickles, pennies); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/StandardChangeCalculator.cs b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/StandardChangeCalculator.cs new file mode 100644 index 00000000..49697581 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/ChangeCalculators/StandardChangeCalculator.cs @@ -0,0 +1,26 @@ +namespace CashDrawer.Core.ChangeCalculators +{ + public class StandardChangeCalculator : IChangeCalculator + { + public Change GetChange(decimal due, decimal paid) + { + var duePennies = (int) ((paid - due) * 100); + + var dollars = duePennies / 100; + duePennies -= dollars * 100; + + var quarters = duePennies / 25; + duePennies -= quarters * 25; + + var dimes = duePennies / 10; + duePennies -= dimes * 10; + + var nickles = duePennies / 5; + duePennies -= nickles * 5; + + var pennies = duePennies; + + return new Change(dollars, quarters, dimes, nickles, pennies); + } + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/ChangeProcessor.cs b/CreativeCashDrawer/CashDrawer.Core/ChangeProcessor.cs new file mode 100644 index 00000000..3b20a3b4 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/ChangeProcessor.cs @@ -0,0 +1,42 @@ +using CashDrawer.Core.ChangeCalculatorFactories; +using CashDrawer.Core.Readers; +using CashDrawer.Core.Writers; + +namespace CashDrawer.Core +{ + public class ChangeProcessor + { + private readonly IChangeCalculatorFactory _changeCalculatorFactory; + + + public ChangeProcessor(IChangeCalculatorFactory changeCalculatorFactory) + { + _changeCalculatorFactory = changeCalculatorFactory; + } + + + public void Process(IInputReader reader, IOutputWriter writer) + { + while(reader.HaveMore) + { + var input = reader.Next(); + + if (input.HasError) + { + writer.WriteError(input.Error); + } + else if (input.Paid < input.Due) + { + writer.WriteError("Underpayment"); + } + else + { + var calculator = _changeCalculatorFactory.GetChangeCalculator(input.Due); + var change = calculator.GetChange(input.Due, input.Paid); + writer.Write(change); + } + } + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/Readers/IInputReader.cs b/CreativeCashDrawer/CashDrawer.Core/Readers/IInputReader.cs new file mode 100644 index 00000000..2b495e4c --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/Readers/IInputReader.cs @@ -0,0 +1,9 @@ +namespace CashDrawer.Core.Readers +{ + public interface IInputReader + { + bool HaveMore { get; } + + ReadResult Next(); + } +} \ No newline at end of file diff --git a/CreativeCashDrawer/CashDrawer.Core/Readers/ReadResult.cs b/CreativeCashDrawer/CashDrawer.Core/Readers/ReadResult.cs new file mode 100644 index 00000000..bd7ca798 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/Readers/ReadResult.cs @@ -0,0 +1,30 @@ +namespace CashDrawer.Core.Readers +{ + public record ReadResult + { + public readonly decimal Due; + public readonly decimal Paid; + public readonly string Error; + + public bool HasError => Error != null; + + + public ReadResult(decimal due, decimal paid, string error) + { + Due = due; + Paid = paid; + Error = error; + } + + public static ReadResult Failed(string error) + { + return new ReadResult(0, 0, error); + } + + public static ReadResult Ok(decimal due, decimal paid) + { + return new ReadResult(due, paid, null); + } + + } +} diff --git a/CreativeCashDrawer/CashDrawer.Core/Writers/IOutputWriter.cs b/CreativeCashDrawer/CashDrawer.Core/Writers/IOutputWriter.cs new file mode 100644 index 00000000..1b18f134 --- /dev/null +++ b/CreativeCashDrawer/CashDrawer.Core/Writers/IOutputWriter.cs @@ -0,0 +1,9 @@ +namespace CashDrawer.Core.Writers +{ + public interface IOutputWriter + { + void Write(Change change); + + void WriteError(string error); + } +} \ No newline at end of file diff --git a/CreativeCashDrawer/CreativeCashDrawer.sln b/CreativeCashDrawer/CreativeCashDrawer.sln new file mode 100644 index 00000000..915aa633 --- /dev/null +++ b/CreativeCashDrawer/CreativeCashDrawer.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31515.178 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{259542DD-EC4D-411C-9E9A-A08F5B8A630B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDrawer.Core.Tests", "CashDrawer.Core.Tests\CashDrawer.Core.Tests.csproj", "{28A98E1E-7AC6-456C-B734-5EC2CD96371B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDrawer.App.Tests", "CashDrawer.App.Tests\CashDrawer.App.Tests.csproj", "{37D020EC-AA64-4FD6-8244-1944320A4046}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDrawer.Core", "CashDrawer.Core\CashDrawer.Core.csproj", "{622661BD-96C2-449D-91E6-D8083E59D54F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDrawer.App", "CashDrawer.App\CashDrawer.App.csproj", "{3AC14D68-CD7C-43B0-AD25-CBA0BEBB9544}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {28A98E1E-7AC6-456C-B734-5EC2CD96371B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28A98E1E-7AC6-456C-B734-5EC2CD96371B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28A98E1E-7AC6-456C-B734-5EC2CD96371B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28A98E1E-7AC6-456C-B734-5EC2CD96371B}.Release|Any CPU.Build.0 = Release|Any CPU + {37D020EC-AA64-4FD6-8244-1944320A4046}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37D020EC-AA64-4FD6-8244-1944320A4046}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37D020EC-AA64-4FD6-8244-1944320A4046}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37D020EC-AA64-4FD6-8244-1944320A4046}.Release|Any CPU.Build.0 = Release|Any CPU + {622661BD-96C2-449D-91E6-D8083E59D54F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {622661BD-96C2-449D-91E6-D8083E59D54F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {622661BD-96C2-449D-91E6-D8083E59D54F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {622661BD-96C2-449D-91E6-D8083E59D54F}.Release|Any CPU.Build.0 = Release|Any CPU + {3AC14D68-CD7C-43B0-AD25-CBA0BEBB9544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AC14D68-CD7C-43B0-AD25-CBA0BEBB9544}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AC14D68-CD7C-43B0-AD25-CBA0BEBB9544}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AC14D68-CD7C-43B0-AD25-CBA0BEBB9544}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {28A98E1E-7AC6-456C-B734-5EC2CD96371B} = {259542DD-EC4D-411C-9E9A-A08F5B8A630B} + {37D020EC-AA64-4FD6-8244-1944320A4046} = {259542DD-EC4D-411C-9E9A-A08F5B8A630B} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E7558B04-4ECE-4672-BFB7-348E2A26BDC2} + EndGlobalSection +EndGlobal