From 0ed0eeba3ea519ede1b781fa4cbebb2212a626fe Mon Sep 17 00:00:00 2001
From: Sean Callan <seancallan@gmail.com>
Date: Sun, 29 Apr 2018 13:50:10 -0600
Subject: [PATCH] New Simple Bank exercise

---
 README.md                              |  4 ++
 simple_bank/.formatter.exs             |  4 ++
 simple_bank/.gitignore                 | 24 ++++++++++
 simple_bank/README.md                  | 10 ++++
 simple_bank/config/config.exs          | 30 ++++++++++++
 simple_bank/lib/simple_bank.ex         | 34 ++++++++++++++
 simple_bank/lib/simple_bank/account.ex |  5 ++
 simple_bank/mix.exs                    | 28 ++++++++++++
 simple_bank/test/simple_bank_test.exs  | 63 ++++++++++++++++++++++++++
 simple_bank/test/test_helper.exs       |  1 +
 10 files changed, 203 insertions(+)
 create mode 100644 simple_bank/.formatter.exs
 create mode 100644 simple_bank/.gitignore
 create mode 100644 simple_bank/README.md
 create mode 100644 simple_bank/config/config.exs
 create mode 100644 simple_bank/lib/simple_bank.ex
 create mode 100644 simple_bank/lib/simple_bank/account.ex
 create mode 100644 simple_bank/mix.exs
 create mode 100644 simple_bank/test/simple_bank_test.exs
 create mode 100644 simple_bank/test/test_helper.exs

diff --git a/README.md b/README.md
index 545d5a9..a6ad5a5 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,10 @@ Each exercise is created as a standalone Mix project requiring a varying degree
 
   > Provided with a string of characters ("aabbc"), print all possible palindrome premutations ("abcba", "bacab") to IO.
 
+  - [Simple Bank](/tree/master/simple_bank) _added 2018-04-30_
+
+  > Build a bank using a GenServer to support account registration, deposits, withdrawls, and account balance inquiries.
+
 ## Contributions
 
 We'd love to hear your feedback on how these exercises are working for you.
diff --git a/simple_bank/.formatter.exs b/simple_bank/.formatter.exs
new file mode 100644
index 0000000..525446d
--- /dev/null
+++ b/simple_bank/.formatter.exs
@@ -0,0 +1,4 @@
+# Used by "mix format"
+[
+  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
+]
diff --git a/simple_bank/.gitignore b/simple_bank/.gitignore
new file mode 100644
index 0000000..2490feb
--- /dev/null
+++ b/simple_bank/.gitignore
@@ -0,0 +1,24 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where 3rd-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Ignore package tarball (built via "mix hex.build").
+simple_bank-*.tar
+
diff --git a/simple_bank/README.md b/simple_bank/README.md
new file mode 100644
index 0000000..0b8ff34
--- /dev/null
+++ b/simple_bank/README.md
@@ -0,0 +1,10 @@
+# SimpleBank
+
+In this lesson we're going to practice building a GenServer to mimic a bank.
+Our bank is expected to allow new accounts, deposits, withdrawls, and balance requests.
+
+To verify your code works and the tests pass run:
+
+```shell
+$ mix test
+```
diff --git a/simple_bank/config/config.exs b/simple_bank/config/config.exs
new file mode 100644
index 0000000..21632ba
--- /dev/null
+++ b/simple_bank/config/config.exs
@@ -0,0 +1,30 @@
+# This file is responsible for configuring your application
+# and its dependencies with the aid of the Mix.Config module.
+use Mix.Config
+
+# This configuration is loaded before any dependency and is restricted
+# to this project. If another project depends on this project, this
+# file won't be loaded nor affect the parent project. For this reason,
+# if you want to provide default values for your application for
+# 3rd-party users, it should be done in your "mix.exs" file.
+
+# You can configure your application as:
+#
+#     config :simple_bank, key: :value
+#
+# and access this configuration in your application as:
+#
+#     Application.get_env(:simple_bank, :key)
+#
+# You can also configure a 3rd-party app:
+#
+#     config :logger, level: :info
+#
+
+# It is also possible to import configuration files, relative to this
+# directory. For example, you can emulate configuration per environment
+# by uncommenting the line below and defining dev.exs, test.exs and such.
+# Configuration from the imported file will override the ones defined
+# here (which is why it is important to import them last).
+#
+#     import_config "#{Mix.env}.exs"
diff --git a/simple_bank/lib/simple_bank.ex b/simple_bank/lib/simple_bank.ex
new file mode 100644
index 0000000..8b34e3f
--- /dev/null
+++ b/simple_bank/lib/simple_bank.ex
@@ -0,0 +1,34 @@
+defmodule SimpleBank do
+  @moduledoc """
+  SimpleBank is a GenServer charading as a bank.
+  """
+  use GenServer
+
+  def start_link(initial_state \\ %{}) do
+    GenServer.start_link(__MODULE__, initial_state)
+  end
+
+  @spec register(pid(), String.t()) :: {:ok, String.t()} | {:error, reason}
+  def register(bank_pid, name) do
+  end
+
+  @spec deposit(pid(), String.t(), pos_integer()} :: {:ok, pos_integer()} | {:error, reason}
+  def deposit(bank_pid, account_id, amount) when is_integer(amount) and amount > 0 do
+  end
+
+  def deposit(_bank_pid, _account_id, _amount) do
+    {:error, :missing_account}
+  end
+
+  @spec deposit(pid(), String.t()} :: {:ok, pos_integer} | {:error, reason}
+  def balance(bank_pid, account_id) do
+  end
+
+  @spec withdrawl(pid(), String.t(), pos_integer()} :: {:ok, {pos_integer(), pos_integer()}} | {:error, reason}
+  def withdrawl(bank_pid, account_id, amount) do
+  end
+
+  def init(initial_state) do
+    {:ok, initial_state}
+  end
+end
diff --git a/simple_bank/lib/simple_bank/account.ex b/simple_bank/lib/simple_bank/account.ex
new file mode 100644
index 0000000..bc91847
--- /dev/null
+++ b/simple_bank/lib/simple_bank/account.ex
@@ -0,0 +1,5 @@
+defmodule SimpleBank.Account do
+  @type t :: %__MODULE__{balance: integer(), id: String.t(), name: String.t()}
+
+  defstruct [:balance, :id, :name]
+end
diff --git a/simple_bank/mix.exs b/simple_bank/mix.exs
new file mode 100644
index 0000000..4bd902a
--- /dev/null
+++ b/simple_bank/mix.exs
@@ -0,0 +1,28 @@
+defmodule SimpleBank.MixProject do
+  use Mix.Project
+
+  def project do
+    [
+      app: :simple_bank,
+      version: "0.1.0",
+      elixir: "~> 1.6",
+      start_permanent: Mix.env() == :prod,
+      deps: deps()
+    ]
+  end
+
+  # Run "mix help compile.app" to learn about applications.
+  def application do
+    [
+      extra_applications: [:logger]
+    ]
+  end
+
+  # Run "mix help deps" to learn about dependencies.
+  defp deps do
+    [
+      # {:dep_from_hexpm, "~> 0.3.0"},
+      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
+    ]
+  end
+end
diff --git a/simple_bank/test/simple_bank_test.exs b/simple_bank/test/simple_bank_test.exs
new file mode 100644
index 0000000..170e06e
--- /dev/null
+++ b/simple_bank/test/simple_bank_test.exs
@@ -0,0 +1,63 @@
+defmodule SimpleBankTest do
+  use ExUnit.Case, async: false
+
+  alias SimpleBank.Account
+
+  setup do
+    {:ok, bank_pid} = SimpleBank.start_link([%Account{balance: 100, id: "test_id", name: "Test"}])
+    {:ok, bank: bank_pid}
+  end
+
+  describe "register/2" do
+    test "creates a new account and generates an account id", %{bank: bank_pid} do
+      {:ok, account_id} = SimpleBank.register(bank_pid, "Another Test Account")
+      assert is_binary(account_id)
+    end
+
+    test "raises an error for existing account names", %{bank: bank_pid}  do
+      {:error, :existing_account} = SimpleBank.register(bank_pid, "Test")
+    end
+  end
+
+  describe "deposit/3" do
+    test "increases the account balance by the deposited amount", %{bank: bank_pid}  do
+      assert {:ok, 10} == SimpleBank.deposit(bank_pid, "test_id", 10)
+    end
+
+    test "does not allow deposits of negative amounts", %{bank: bank_pid}  do
+      assert {:error, :pos_integer_only} == SimpleBank.deposit(bank_pid, "test_id", -1)
+    end
+
+    test "raises an error if the account does not exist", %{bank: bank_pid}  do
+      assert {:error, :missing_account} == SimpleBank.deposit(bank_pid, "doesnotexist", 10)
+    end
+  end
+
+  describe "balance/2" do
+    test "returns the current account balance", %{bank: bank_pid}  do
+      assert {:ok, 110} == SimpleBank.deposit(bank_pid, "test_id", 10)
+    end
+
+    test "raises an error if the account does not exist", %{bank: bank_pid}  do
+      assert {:error, :missing_account} == SimpleBank.balance(bank_pid, "doesnotexist")
+    end
+  end
+
+  describe "withdrawl/3" do
+    test "decreases the account balance by the withdrawn amount", %{bank: bank_pid}  do
+      assert {:ok, 100} == SimpleBank.withdrawl(bank_pid, "test_id", 10)
+    end
+
+    test "does not negative ammount balances", %{bank: bank_pid}  do
+      assert {:error, :insufficient_funds} == SimpleBank.withdrawl(bank_pid, "test_id", -1)
+    end
+
+    test "does not allow withdrawls of negative amounts", %{bank: bank_pid}  do
+      assert {:error, :pos_integer_only} == SimpleBank.withdrawl(bank_pid, "test_id", 1000)
+    end
+
+    test "raises an error if the account does not exist", %{bank: bank_pid}  do
+      assert {:error, :missing_account} == SimpleBank.deposit(bank_pid, "doesnotexist", 10)
+    end
+  end
+end
diff --git a/simple_bank/test/test_helper.exs b/simple_bank/test/test_helper.exs
new file mode 100644
index 0000000..869559e
--- /dev/null
+++ b/simple_bank/test/test_helper.exs
@@ -0,0 +1 @@
+ExUnit.start()
