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()