From fa8814d8bdf51fe627ed55fb5143aafa09456709 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Fri, 20 Sep 2024 17:08:42 -0400 Subject: [PATCH] Add bank-account exercise (#347) * Add bank-account exercise Without parallelism/concurrency * instructions append: no parallelism * move exercise earlier in config --- config.json | 8 ++ .../bank-account/.docs/instructions.append.md | 6 + .../bank-account/.docs/instructions.md | 10 ++ .../bank-account/.docs/introduction.md | 20 ++++ .../practice/bank-account/.meta/config.json | 17 +++ .../bank-account/.meta/example.coffee | 29 +++++ .../practice/bank-account/.meta/tests.toml | 61 +++++++++++ .../practice/bank-account/bank-account.coffee | 14 +++ .../bank-account/bank-account.spec.coffee | 103 ++++++++++++++++++ 9 files changed, 268 insertions(+) create mode 100644 exercises/practice/bank-account/.docs/instructions.append.md create mode 100644 exercises/practice/bank-account/.docs/instructions.md create mode 100644 exercises/practice/bank-account/.docs/introduction.md create mode 100644 exercises/practice/bank-account/.meta/config.json create mode 100644 exercises/practice/bank-account/.meta/example.coffee create mode 100644 exercises/practice/bank-account/.meta/tests.toml create mode 100644 exercises/practice/bank-account/bank-account.coffee create mode 100644 exercises/practice/bank-account/bank-account.spec.coffee diff --git a/config.json b/config.json index 6636273..8154347 100644 --- a/config.json +++ b/config.json @@ -72,6 +72,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "bank-account", + "name": "Bank Account", + "uuid": "27f36909-8005-4537-80b4-4c81a3325b5b", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, { "slug": "beer-song", "name": "Beer Song", diff --git a/exercises/practice/bank-account/.docs/instructions.append.md b/exercises/practice/bank-account/.docs/instructions.append.md new file mode 100644 index 0000000..c27f15d --- /dev/null +++ b/exercises/practice/bank-account/.docs/instructions.append.md @@ -0,0 +1,6 @@ +# ignore + +## CoffeeScript-specific Instructions + +For this exercise, we will not be testing parallelism. +Your task is to implement the BankAccount methods without worrying about parallel execution. diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md new file mode 100644 index 0000000..7398fbe --- /dev/null +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -0,0 +1,10 @@ +# Instructions + +Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. + +As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. +For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there are no [race conditions][wikipedia] between when you read the account balance and set the new balance. + +It should be possible to close an account; operations against a closed account must fail. + +[wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software diff --git a/exercises/practice/bank-account/.docs/introduction.md b/exercises/practice/bank-account/.docs/introduction.md new file mode 100644 index 0000000..650b5d9 --- /dev/null +++ b/exercises/practice/bank-account/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +After years of filling out forms and waiting, you've finally acquired your banking license. +This means you are now officially eligible to open your own bank, hurray! + +Your first priority is to get the IT systems up and running. +After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits. + +Since you couldn't be bothered writing tests, you invite some friends to help test the system. +However, after just five minutes, one of your friends claims they've lost money! +While you're confident your code is bug-free, you start looking through the logs to investigate. + +Ah yes, just as you suspected, your friend is at fault! +They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_. +Who would do such a thing? + +While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this. +Thus, no parallel banking support, no go-live signal. +Sighing, you create a mental note to work on this tomorrow. +This will set your launch date back at _least_ one more day, but well... diff --git a/exercises/practice/bank-account/.meta/config.json b/exercises/practice/bank-account/.meta/config.json new file mode 100644 index 0000000..5c6645f --- /dev/null +++ b/exercises/practice/bank-account/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "bank-account.coffee" + ], + "test": [ + "bank-account.spec.coffee" + ], + "example": [ + ".meta/example.coffee" + ] + }, + "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!" +} diff --git a/exercises/practice/bank-account/.meta/example.coffee b/exercises/practice/bank-account/.meta/example.coffee new file mode 100644 index 0000000..ec4bee4 --- /dev/null +++ b/exercises/practice/bank-account/.meta/example.coffee @@ -0,0 +1,29 @@ +class BankAccount + constructor: -> + @_open = false + + open: -> + throw Error 'account already open' if @_open + @_open = true + @_balance = 0 + + close: -> + throw Error 'account not open' unless @_open + @_open = false + + balance: -> + throw Error 'account not open' unless @_open + @_balance + + deposit: (money) -> + throw Error 'account not open' unless @_open + throw Error "amount must be greater than 0" unless money > 0 + @_balance += money + + withdraw: (money) -> + throw Error 'account not open' unless @_open + throw Error "amount must be greater than 0" unless money > 0 + throw Error "amount must be less than balance" unless money <= @_balance + @_balance -= money + +module.exports = BankAccount diff --git a/exercises/practice/bank-account/.meta/tests.toml b/exercises/practice/bank-account/.meta/tests.toml new file mode 100644 index 0000000..4e42d4d --- /dev/null +++ b/exercises/practice/bank-account/.meta/tests.toml @@ -0,0 +1,61 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[983a1528-4ceb-45e5-8257-8ce01aceb5ed] +description = "Newly opened account has zero balance" + +[e88d4ec3-c6bf-4752-8e59-5046c44e3ba7] +description = "Single deposit" + +[3d9147d4-63f4-4844-8d2b-1fee2e9a2a0d] +description = "Multiple deposits" + +[08f1af07-27ae-4b38-aa19-770bde558064] +description = "Withdraw once" + +[6f6d242f-8c31-4ac6-8995-a90d42cad59f] +description = "Withdraw twice" + +[45161c94-a094-4c77-9cec-998b70429bda] +description = "Can do multiple operations sequentially" + +[f9facfaa-d824-486e-8381-48832c4bbffd] +description = "Cannot check balance of closed account" + +[7a65ba52-e35c-4fd2-8159-bda2bde6e59c] +description = "Cannot deposit into closed account" + +[a0a1835d-faae-4ad4-a6f3-1fcc2121380b] +description = "Cannot deposit into unopened account" + +[570dfaa5-0532-4c1f-a7d3-0f65c3265608] +description = "Cannot withdraw from closed account" + +[c396d233-1c49-4272-98dc-7f502dbb9470] +description = "Cannot close an account that was not opened" + +[c06f534f-bdc2-4a02-a388-1063400684de] +description = "Cannot open an already opened account" + +[0722d404-6116-4f92-ba3b-da7f88f1669c] +description = "Reopened account does not retain balance" + +[ec42245f-9361-4341-8231-a22e8d19c52f] +description = "Cannot withdraw more than deposited" + +[4f381ef8-10ef-4507-8e1d-0631ecc8ee72] +description = "Cannot withdraw negative" + +[d45df9ea-1db0-47f3-b18c-d365db49d938] +description = "Cannot deposit negative" + +[ba0c1e0b-0f00-416f-8097-a7dfc97871ff] +description = "Can handle concurrent transactions" diff --git a/exercises/practice/bank-account/bank-account.coffee b/exercises/practice/bank-account/bank-account.coffee new file mode 100644 index 0000000..e9024ea --- /dev/null +++ b/exercises/practice/bank-account/bank-account.coffee @@ -0,0 +1,14 @@ +class BankAccount + constructor: (args) -> + + open: () -> + + close: () -> + + balance: () -> + + deposit: (args) -> + + withdraw: (args) -> + +module.exports = BankAccount diff --git a/exercises/practice/bank-account/bank-account.spec.coffee b/exercises/practice/bank-account/bank-account.spec.coffee new file mode 100644 index 0000000..dfb039d --- /dev/null +++ b/exercises/practice/bank-account/bank-account.spec.coffee @@ -0,0 +1,103 @@ +BankAccount = require './bank-account' + +describe 'BankAccount', -> + it 'Newly opened account has zero balance', -> + bankAccount = new BankAccount + bankAccount.open() + expect(bankAccount.balance()).toEqual 0 + + xit "Single deposit", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 100 + expect(bankAccount.balance()).toEqual 100 + + xit "Multiple deposits", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 100 + bankAccount.deposit 50 + expect(bankAccount.balance()).toEqual 150 + + xit "Withdraw once", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 100 + bankAccount.withdraw 75 + expect(bankAccount.balance()).toEqual 25 + + xit "Withdraw twice", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 100 + bankAccount.withdraw 80 + bankAccount.withdraw 20 + expect(bankAccount.balance()).toEqual 0 + + xit "Can do multiple operations sequentially", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 100 + bankAccount.deposit 110 + bankAccount.withdraw 200 + bankAccount.deposit 60 + bankAccount.withdraw 50 + expect(bankAccount.balance()).toEqual 20 + + xit "Cannot check balance of closed account", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.close() + expect( -> bankAccount.balance()).toThrow "account not open" + + xit "Cannot deposit into closed account", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.close() + expect( -> bankAccount.deposit 50).toThrow "account not open" + + xit "Cannot deposit into unopened account", -> + bankAccount = new BankAccount + expect( -> bankAccount.deposit 50).toThrow "account not open" + + xit "Cannot withdraw from closed account", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.close() + expect( -> bankAccount.withdraw 50).toThrow "account not open" + + xit "Cannot close an account that was not opened", -> + bankAccount = new BankAccount + expect( -> bankAccount.close()).toThrow "account not open" + + xit "Cannot open an already opened account", -> + bankAccount = new BankAccount + bankAccount.open() + expect( -> bankAccount.open()).toThrow "account already open" + + + xit "Reopened account does not retain balance", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 50 + bankAccount.close() + bankAccount.open() + expect(bankAccount.balance()).toEqual 0 + + xit "Cannot withdraw more than deposited", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 25 + expect( -> bankAccount.withdraw 50).toThrow "amount must be less than balance" + + xit "Cannot withdraw negative", -> + bankAccount = new BankAccount + bankAccount.open() + bankAccount.deposit 100 + expect( -> bankAccount.withdraw -50).toThrow "amount must be greater than 0" + + xit "Cannot deposit negative", -> + bankAccount = new BankAccount + bankAccount.open() + expect( -> bankAccount.deposit -50).toThrow "amount must be greater than 0" +