diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..a7379978 Binary files /dev/null and b/.DS_Store differ diff --git a/lib/account.rb b/lib/account.rb index e69de29b..cdc74bf9 100644 --- a/lib/account.rb +++ b/lib/account.rb @@ -0,0 +1,57 @@ +require 'CSV' +module Bank + class Account + attr_reader :id, :balance, :date + + def initialize(id, balance, date) + raise ArgumentError.new("balance must be >= 0") if balance < 0 + @id = id.to_i + @balance = balance.to_i + @date = date + end + + def withdraw(amount, fee = 0, min_balance = 0) + raise ArgumentError.new ("Nice try! You can't withdraw negative money, and withdrawing $0 would be a waste of our time.") if amount <= 0 + + balance_would_be = @balance - amount - fee + + if balance_would_be < min_balance + puts "You don't have enough money to withdraw #{amount} cents." + + puts "There would be a #{fee} convenience fees." + + puts "You must maintain a minimum balance of #{min_balance} cents." + else + @balance = balance_would_be + end + return @balance + end + + def deposit(amount) + raise ArgumentError.new ("We don't want your debt (negative money) here!") if amount < 0 + + @balance += amount + return @balance + end + + # returns a collection of Account instances, representing all of the Accounts described in CSV + def self.all(csv_filename) + CSV.read(csv_filename).map do |row| + id = row[0].to_i + balance = row[1].to_i + date = row[2] + self.new(id, balance, date) + end + #return accounts + end + + def self.find(csv_filename, id_to_find) + fail = proc { + raise ArgumentError.new("There is no client with the id you entered.") + } + self.all(csv_filename).find(fail) do |acct| + acct.id.to_i == id_to_find.to_i + end + end + end +end diff --git a/lib/checking_account.rb b/lib/checking_account.rb new file mode 100644 index 00000000..7f907729 --- /dev/null +++ b/lib/checking_account.rb @@ -0,0 +1,32 @@ +module Bank + class CheckingAccount < Account + def initialize(id, balance, date) + super(id, balance, date) + @withdrawals_by_check = 0 + end + + def withdraw(amount, fee = 100, min_balance = 0) + super(amount, fee, min_balance) + return @balance + end + + def withdraw_using_check(amount) + balance_before_withdrawal = @balance + + if @withdrawals_by_check > 2 + self.withdraw(amount, fee = 200, min_balance = -1000 ) + else + self.withdraw(amount, fee = 0, min_balance = -1000 ) + end + + if @balance < balance_before_withdrawal + @withdrawals_by_check += 1 + end + @balance = balance + end + + def reset_checks + @withdrawals_by_check = 0 + end + end +end diff --git a/lib/savings_account.rb b/lib/savings_account.rb new file mode 100644 index 00000000..c18b349d --- /dev/null +++ b/lib/savings_account.rb @@ -0,0 +1,25 @@ + +module Bank + class SavingsAccount < Account + def initialize(id, balance, date) + + raise ArgumentError.new("balance must be at least $10") if balance < 1000 + + super(id, balance, date) + end + + def withdraw(amount) + fee = 200 + min_balance = 1000 + super(amount, fee, min_balance) + end + + def add_interest(rate) + raise ArgumentError.new("Negative interest rates are not only bad for your finances; they are illegal") if rate < 0 + + interest = @balance * rate/100 + @balance += interest + return interest + end + end +end diff --git a/specs/account_spec.rb b/specs/account_spec.rb index 6c399139..1cfaf6ec 100644 --- a/specs/account_spec.rb +++ b/specs/account_spec.rb @@ -2,8 +2,10 @@ require 'minitest/reporters' require 'minitest/skip_dsl' require_relative '../lib/account' +require 'CSV' +require 'skip' -describe "Wave 1" do +xdescribe "Wave 1" do describe "Account#initialize" do it "Takes an ID and an initial balance" do id = 1337 @@ -17,6 +19,7 @@ account.balance.must_equal balance end + it "Raises an ArgumentError when created with a negative balance" do # Note: we haven't talked about procs yet. You can think # of them like blocks that sit by themselves. @@ -27,6 +30,7 @@ }.must_raise ArgumentError end + it "Can be created with a balance of 0" do # If this raises, the test will fail. No 'must's needed! Bank::Account.new(1337, 0) @@ -56,11 +60,12 @@ updated_balance.must_equal expected_balance end + + it "Outputs a warning if the account would go negative" do start_balance = 100.0 withdrawal_amount = 200.0 - account = Bank::Account.new(1337, start_balance) - + account = Bank::Account.new(1337, start_balance,) # Another proc! This test expects something to be printed # to the terminal, using 'must_output'. /.+/ is a regular # expression matching one or more characters - as long as @@ -101,6 +106,7 @@ end end + describe "Account#deposit" do it "Increases the balance" do start_balance = 100.0 @@ -136,36 +142,90 @@ end end + # TODO: change 'xdescribe' to 'describe' to run these tests -xdescribe "Wave 2" do +describe "Wave 2" do describe "Account.all" do - it "Returns an array of all accounts" do - # TODO: Your test code here! - # Useful checks might include: - # - Account.all returns an array - # - Everything in the array is an Account - # - The number of accounts is correct - # - The ID and balance of the first and last - # accounts match what's in the CSV file - # Feel free to split this into multiple tests if needed + + # - Account.all returns an array + it "Returns an array" do + accounts = Bank::Account.all("support/accounts.csv") + accounts.must_be_kind_of (Array) + end + + it "contains only accounts" do + accounts = Bank::Account.all("support/accounts.csv") + accounts.each do |acct| + acct.must_be_kind_of Bank::Account + end + end + + it "has the right number of elements" do + accounts = Bank::Account.all("support/accounts.csv") + + accounts.length == CSV.read("support/accounts.csv").length + end + + it "matches the first and last row" do + accounts = Bank::Account.all("support/accounts.csv") + compare_to = CSV.read("support/accounts.csv") + + accounts.first.id.must_equal compare_to.first.first.to_i + + accounts.first.balance.must_equal compare_to.first[1].to_i + + accounts.last.balance.must_equal compare_to.last[1].to_i + + accounts.last.balance.must_equal compare_to.last[1].to_i end end + describe "Account.find" do it "Returns an account that exists" do - # TODO: Your test code here! + csv_filename = "support/accounts.csv" + + account = Bank::Account.find(csv_filename, 1212) + + acct_arr = [account.id.to_s, account.balance.to_s, account.date] + + exists = false + CSV.open(csv_filename).each do |row| + if acct_arr == row + exists = true + end + end + exists.must_equal true end + it "Can find the first account from the CSV" do - # TODO: Your test code here! + csv_filename = "support/accounts.csv" + first_row = CSV.read(csv_filename).first + + account = Bank::Account.find(csv_filename, first_row.first) + + acct_arr = [account.id.to_s, account.balance.to_s, account.date] + + acct_arr.must_equal first_row + end it "Can find the last account from the CSV" do - # TODO: Your test code here! + csv_filename = "support/accounts.csv" + last_row = CSV.read(csv_filename).last + + account = Bank::Account.find(csv_filename, last_row.first) + + acct_arr = [account.id.to_s, account.balance.to_s, account.date] + + acct_arr.must_equal last_row end it "Raises an error for an account that doesn't exist" do - # TODO: Your test code here! + proc { + Bank::Account.find("support/accounts.csv", "5892") + }.must_raise ArgumentError end end end diff --git a/specs/checking_account_spec.rb b/specs/checking_account_spec.rb index 7f95339e..c6d71348 100644 --- a/specs/checking_account_spec.rb +++ b/specs/checking_account_spec.rb @@ -1,80 +1,215 @@ require 'minitest/autorun' require 'minitest/reporters' require 'minitest/skip_dsl' +require_relative '../lib/checking_account' + -# TODO: uncomment the next line once you start wave 3 and add lib/checking_account.rb -# require_relative '../lib/checking_account' # Because a CheckingAccount is a kind # of Account, and we've already tested a bunch of functionality # on Account, we effectively get all that testing for free! # Here we'll only test things that are different. -# TODO: change 'xdescribe' to 'describe' to run these tests -xdescribe "CheckingAccount" do +describe "CheckingAccount" do describe "#initialize" do # Check that a CheckingAccount is in fact a kind of account it "Is a kind of Account" do - account = Bank::CheckingAccount.new(12345, 100.0) + account = Bank::CheckingAccount.new(12345, 100.0,"NA") account.must_be_kind_of Bank::Account end end describe "#withdraw" do it "Applies a $1 fee each time" do - # TODO: Your test code here! - end + start_balance = 1399 + withdrawal_amount = 100 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + updated_balance = account.withdraw(withdrawal_amount) - it "Doesn't modify the balance if the fee would put it negative" do - # TODO: Your test code here! + # Both the value returned and the balance in the account + # must be un-modified. + updated_balance.must_equal (start_balance - 100 - withdrawal_amount) + account.balance.must_equal (start_balance - 100-withdrawal_amount) end end - describe "#withdraw_using_check" do - it "Reduces the balance" do - # TODO: Your test code here! - end + it "Doesn't modify the balance if the fee would put it negative" do + start_balance = 1399 + withdrawal_amount = 1300 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") - it "Returns the modified balance" do - # TODO: Your test code here! - end + updated_balance = account.withdraw(withdrawal_amount) - it "Allows the balance to go down to -$10" do - # TODO: Your test code here! - end + # Both the value returned and the balance in the account + # must be un-modified. + updated_balance.must_equal start_balance + account.balance.must_equal start_balance - it "Outputs a warning if the account would go below -$10" do - # TODO: Your test code here! - end + end +end - it "Doesn't modify the balance if the account would go below -$10" do - # TODO: Your test code here! - end +describe "#withdraw_using_check" do + it "Reduces the balance" do + start_balance = 1399 + withdrawal_amount = 1300 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") - it "Requires a positive withdrawal amount" do - # TODO: Your test code here! - end + updated_balance = account.withdraw_using_check(withdrawal_amount) - it "Allows 3 free uses" do - # TODO: Your test code here! - end + # Both the value returned and the balance in the account + # must be un-modified. + (updated_balance - start_balance).wont_equal + ((updated_balance - start_balance).abs) + end + + it "Returns the modified balance" do + start_balance = 1400 + withdrawal_amount = 1300 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + updated_balance = account.withdraw_using_check(withdrawal_amount) + + expected_balance = start_balance - withdrawal_amount + + updated_balance.must_equal expected_balance #account.balance.must_equal expected_balance - it "Applies a $2 fee after the third use" do - # TODO: Your test code here! - end end - describe "#reset_checks" do - it "Can be called without error" do - # TODO: Your test code here! - end + it "Allows the balance to go down to -$10" do + start_balance = 1400 + withdrawal_amount = 1410 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + updated_balance = account.withdraw_using_check(withdrawal_amount) - it "Makes the next three checks free if less than 3 checks had been used" do - # TODO: Your test code here! - end + expected_balance = start_balance - withdrawal_amount + + updated_balance.must_equal expected_balance #account.balance.must_equal expected_balance + end + + it "Outputs a warning if the account would go below -$10" do + start_balance = 1000 + withdrawal_amount = 3000 + account = Bank::CheckingAccount.new(1337, start_balance,"BA") + # Another proc! This test expects something to be printed + # to the terminal, using 'must_output'. /.+/ is a regular + # expression matching one or more characters - as long as + # anything at all is printed out the test will pass. + proc { + account.withdraw_using_check(withdrawal_amount) + }.must_output /.+/ + end + + it "Doesn't modify the balance if the account would go below -$10" do + start_balance = 10000 + withdrawal_amount = 20001 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + updated_balance = account.withdraw_using_check(withdrawal_amount) + + # Both the value returned and the balance in the account + # must be un-modified. + updated_balance.must_equal start_balance + account.balance.must_equal start_balance + + end + + it "Requires a positive withdrawal amount" do + start_balance = 10000 + withdrawal_amount = 0 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + proc { + account.withdraw_using_check(withdrawal_amount) + }.must_raise ArgumentError + end + + it "Allows 3 free uses" do + start_balance = 10000 + withdrawal_amount = 2000 + + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + + 3.times {account.withdraw_using_check(withdrawal_amount)} + updated_balance = account.balance + + expected_balance = start_balance - 3 * withdrawal_amount + + updated_balance.must_equal expected_balance + end + + it "Applies a $2 fee after the third use" do + start_balance = 10000 + withdrawal_amount = 2000 + + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + + 4.times {account.withdraw_using_check(withdrawal_amount)} + updated_balance = account.balance + + expected_balance = start_balance - 4 * withdrawal_amount - 200 + + updated_balance.must_equal expected_balance + end +end + +describe "#reset_checks" do + it "Can be called without error" do + start_balance = 1000000 + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + account.reset_checks + end + + it "Makes the next three checks free if less than 3 checks had been used" do + + start_balance = 10000 + withdrawal_amount = 100 + + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + + 2.times {account.withdraw_using_check(withdrawal_amount)} + + expected_balance = start_balance - 2 * withdrawal_amount + + account.reset_checks + + + 3.times {account.withdraw_using_check(withdrawal_amount)} + + expected_balance = expected_balance - 3 * withdrawal_amount + + updated_balance = account.balance + + + updated_balance.must_equal expected_balance + + + end + + it "Makes the next three checks free if more than 3 checks had been used" do + start_balance = 10000 + withdrawal_amount = 100 + + account = Bank::CheckingAccount.new(1337, start_balance, "NA") + + + 4.times {account.withdraw_using_check(withdrawal_amount)} + + expected_balance = start_balance - 4 * withdrawal_amount - 200 + + account.reset_checks + + + 3.times {account.withdraw_using_check(withdrawal_amount)} + + expected_balance = expected_balance - 3 * withdrawal_amount + + updated_balance = account.balance + + + updated_balance.must_equal expected_balance - it "Makes the next three checks free if more than 3 checks had been used" do - # TODO: Your test code here! - end end end diff --git a/specs/savings_account_spec.rb b/specs/savings_account_spec.rb index 3f4d1e4a..af6c520d 100644 --- a/specs/savings_account_spec.rb +++ b/specs/savings_account_spec.rb @@ -1,58 +1,108 @@ require 'minitest/autorun' require 'minitest/reporters' require 'minitest/skip_dsl' +require_relative '../lib/savings_account' -# TODO: uncomment the next line once you start wave 3 and add lib/savings_account.rb -# require_relative '../lib/savings_account' - -# Because a SavingsAccount is a kind -# of Account, and we've already tested a bunch of functionality -# on Account, we effectively get all that testing for free! -# Here we'll only test things that are different. - -# TODO: change 'xdescribe' to 'describe' to run these tests -xdescribe "SavingsAccount" do +describe "SavingsAccount" do describe "#initialize" do it "Is a kind of Account" do # Check that a SavingsAccount is in fact a kind of account - account = Bank::SavingsAccount.new(12345, 100.0) + account = Bank::SavingsAccount.new(12345, 50000,0) account.must_be_kind_of Bank::Account end it "Requires an initial balance of at least $10" do # TODO: Your test code here! + proc { + Bank::SavingsAccount.new(1337, 500, "NA") + }.must_raise ArgumentError end end describe "#withdraw" do it "Applies a $2 fee each time" do - # TODO: Your test code here! - end + start_balance = 12000 + withdrawal_amount = 1800 + account = Bank::SavingsAccount.new(1337, start_balance, "NA") - it "Outputs a warning if the balance would go below $10" do - # TODO: Your test code here! - end + account.withdraw(withdrawal_amount) - it "Doesn't modify the balance if it would go below $10" do - # TODO: Your test code here! + expected_balance = start_balance - withdrawal_amount - 200 + account.balance.must_equal expected_balance end + end - it "Doesn't modify the balance if the fee would put it below $10" do - # TODO: Your test code here! - end + + it "Outputs a warning if the balance would go below $10" do + start_balance = 1000 + withdrawal_amount = 200 + account = Bank::SavingsAccount.new(1337, start_balance,"NA") + # Another proc! This test expects something to be printed + # to the terminal, using 'must_output'. /.+/ is a regular + # expression matching one or more characters - as long as + # anything at all is printed out the test will pass. + proc { + account.withdraw(withdrawal_amount) + }.must_output /.+/ + end + + it "Doesn't modify the balance if it would go below $10" do + start_balance = 1000 + withdrawal_amount = 200.0 + account = Bank::SavingsAccount.new(1337, start_balance, "NA") + + updated_balance = account.withdraw(withdrawal_amount) + + # Both the value returned and the balance in the account + # must be un-modified. + updated_balance.must_equal start_balance + account.balance.must_equal start_balance + end + + it "Doesn't modify the balance if the fee would put it below $10" do + start_balance = 1399 + withdrawal_amount = 200 + account = Bank::SavingsAccount.new(1337, start_balance, "NA") + + updated_balance = account.withdraw(withdrawal_amount) + + # Both the value returned and the balance in the account + # must be un-modified. + updated_balance.must_equal start_balance + account.balance.must_equal start_balance end describe "#add_interest" do it "Returns the interest calculated" do - # TODO: Your test code here! + start_amount = 10000 + account = Bank::SavingsAccount.new(1337, start_amount, "NA") + rate = 0.25 + expected_interest = start_amount * rate/100 + account.add_interest(rate).must_equal expected_interest end it "Updates the balance with calculated interest" do - # TODO: Your test code here! + start_amount = 10000 + account = Bank::SavingsAccount.new(1337, start_amount, "NA") + rate = 0.25 + + expected_balance = start_amount * (1 + rate/100) + + account.add_interest(rate) + account.balance.must_equal expected_balance end it "Requires a positive rate" do - # TODO: Your test code here! + start_balance = 1000 + rate = -0.25 + account = Bank::SavingsAccount.new(1337, start_balance,"NA") + # Another proc! This test expects something to be printed + # to the terminal, using 'must_output'. /.+/ is a regular + # expression matching one or more characters - as long as + # anything at all is printed out the test will pass. + proc { + account.add_interest(rate) + }.must_raise ArgumentError end end end