diff --git a/lib/account.rb b/lib/account.rb index e69de29b..882337c6 100644 --- a/lib/account.rb +++ b/lib/account.rb @@ -0,0 +1,55 @@ +require 'csv' + +module Bank + class Account + attr_reader :id, :balance, :open_date + def initialize(id, balance, open_date = "") + raise ArgumentError.new("balance must be >= 0") if balance < 0 + + @id = id + @balance = balance + @open_date = open_date + end + + def withdraw(amount) + balance_min = 0 + withdraw_internal(amount, balance_min) + end + + def deposit(amount) + raise ArgumentError.new("deposit must be > 0") if amount <= 0 + @balance += amount + end + + def self.all + accounts = [] + CSV.open("support/accounts.csv").each do |line| + accounts << self.new(line[0].to_f/100, line[1].to_f/100, line[2]) + end + return accounts + end + + def self.find(id) + self.all.each do |account| + return account if account.id == id + end + raise ArgumentError.new("ID does not exist") + end + + private + + def withdraw_internal(amount, balance_min) + raise ArgumentError.new("withdrawl must be > 0") if amount <= 0 + if update_balance?(amount, balance_min) + @balance -= amount + end + return @balance + end + + def update_balance?(amount, balance_min) + return true if @balance - amount >= balance_min + puts "Requested withdrawl amount surpasses allowable account balance." + return false + end + end +end diff --git a/lib/checking_account.rb b/lib/checking_account.rb new file mode 100644 index 00000000..86bc9c71 --- /dev/null +++ b/lib/checking_account.rb @@ -0,0 +1,35 @@ +require_relative 'account' + +module Bank + class CheckingAccount < Account + def initialize(id, balance) + super(id, balance) + @number_of_checks = 0 #free_checks = 3 + end + + def withdraw(amount) + fee = 1.0 + balance_min = 0 + withdraw_internal(amount + fee, balance_min) + end + + def withdraw_using_check(amount) + if @number_of_checks >= 3 + fee = 2.0 + else + fee = 0 + end + balance_min = -10.0 + + if update_balance?((amount + fee), balance_min) + @number_of_checks += 1 + end + + withdraw_internal(amount + fee, balance_min) + end + + def reset_checks + @number_of_checks = 0 + end + end +end diff --git a/lib/savings_account.rb b/lib/savings_account.rb new file mode 100644 index 00000000..ca5f4ce4 --- /dev/null +++ b/lib/savings_account.rb @@ -0,0 +1,24 @@ +require_relative 'account' + +module Bank + class SavingsAccount < Account + def initialize(id, balance) + # Account must have a minimum balance of $10 + raise ArgumentError.new("balance must be >= 10") if balance < 10 + super(id, balance) + end + + def withdraw(amount) + fee = 2.0 + balance_min = 10.0 + withdraw_internal(amount + fee, balance_min) + end + + def add_interest(rate) #input rate is assumed a percentage + raise ArgumentError.new("interest rate must be positive") 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..475084fc 100644 --- a/specs/account_spec.rb +++ b/specs/account_spec.rb @@ -2,6 +2,7 @@ require 'minitest/reporters' require 'minitest/skip_dsl' require_relative '../lib/account' +require 'csv' describe "Wave 1" do describe "Account#initialize" do @@ -18,10 +19,6 @@ 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. - # This code checks that, when the proc is executed, it - # raises an ArgumentError. proc { Bank::Account.new(1337, -100.0) }.must_raise ArgumentError @@ -67,7 +64,7 @@ # anything at all is printed out the test will pass. proc { account.withdraw(withdrawal_amount) - }.must_output /.+/ + }.must_output (/.+/) end it "Doesn't modify the balance if the account would go negative" do @@ -137,35 +134,74 @@ 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 + it "Returns an array" do + accounts = Bank::Account.all + accounts.must_be_instance_of Array + end + + it "Contains only Account elements in the returned array" do + accounts = Bank::Account.all + # Check that each element within the array is an instance of the Account class + accounts.each do |account| + account.must_be_instance_of Bank::Account + end + end + + it "Includes the correct number of accounts in the returned array" do + accounts = Bank::Account.all + num_of_accounts = CSV.read("support/accounts.csv").size + accounts.length.must_equal num_of_accounts + end + + it "Creates a first account with the csv's first listed ID and balance" do + first_id = CSV.read("support/accounts.csv").first[0].to_f/100 + first_balance = CSV.read("support/accounts.csv").first[1].to_f/100 + accounts = Bank::Account.all + + accounts.first.id.must_equal first_id + accounts.first.balance.must_equal first_balance + end + + it "Creates a last account with the csv's last listed ID and balance" do + last_id = CSV.read("support/accounts.csv").last[0].to_f/100 + last_balance = CSV.read("support/accounts.csv").last[1].to_f/100 + accounts = Bank::Account.all + + accounts.last.id.must_equal last_id + accounts.last.balance.must_equal last_balance end end describe "Account.find" do + def find_and_verify_account(id) + account = Bank::Account.find(id) + account.must_be_instance_of Bank::Account + account.id.must_equal id + end + it "Returns an account that exists" do - # TODO: Your test code here! + seventh_id = CSV.read("support/accounts.csv")[6][0].to_f/100 + find_and_verify_account(seventh_id) end it "Can find the first account from the CSV" do - # TODO: Your test code here! + first_id = CSV.read("support/accounts.csv").first[0].to_f/100 + find_and_verify_account(first_id) end it "Can find the last account from the CSV" do - # TODO: Your test code here! + last_id = CSV.read("support/accounts.csv").last[0].to_f/100 + find_and_verify_account(last_id) end it "Raises an error for an account that doesn't exist" do - # TODO: Your test code here! + fake_id = 80 + + proc { + Bank::Account.find(fake_id) + }.must_raise ArgumentError end end end diff --git a/specs/checking_account_spec.rb b/specs/checking_account_spec.rb index 7f95339e..2f89ff0a 100644 --- a/specs/checking_account_spec.rb +++ b/specs/checking_account_spec.rb @@ -1,17 +1,9 @@ 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 @@ -22,59 +14,170 @@ describe "#withdraw" do it "Applies a $1 fee each time" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 25.0 + fee = 1.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + updated_balance = account.withdraw(withdrawal_amount) + expected_balance = start_balance - withdrawal_amount - fee + + # Both the value returned and the balance in the account + # should apply the fee + updated_balance.must_equal expected_balance + account.balance.must_equal expected_balance end it "Doesn't modify the balance if the fee would put it negative" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 100.0 # + $1 fee + account = Bank::CheckingAccount.new(12345, start_balance) + + 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 end describe "#withdraw_using_check" do it "Reduces the balance" do - # TODO: Your test code here! + # Add for possible check fee here or assume brand new account? + start_balance = 100.0 + withdrawal_amount = 25.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + account.withdraw_using_check(withdrawal_amount) + expected_balance = start_balance - withdrawal_amount + + account.balance.must_equal expected_balance end it "Returns the modified balance" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 25.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + updated_balance = account.withdraw_using_check(withdrawal_amount) + expected_balance = start_balance - withdrawal_amount + + updated_balance.must_equal expected_balance end it "Allows the balance to go down to -$10" do - # TODO: Your test code here! + account = Bank::CheckingAccount.new(12345, 100.0) + updated_balance = account.withdraw_using_check(account.balance + 10.0) + + updated_balance.must_equal (-10) + account.balance.must_equal (-10) end it "Outputs a warning if the account would go below -$10" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 200.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + # Anything printed to the console output will pass the test + proc { + account.withdraw_using_check(withdrawal_amount) + }.must_output (/.+/) end it "Doesn't modify the balance if the account would go below -$10" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 200.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + 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 - # TODO: Your test code here! + withdrawal_amount = -25.0 + account = Bank::CheckingAccount.new(12345, 100.0) + + proc { + account.withdraw_using_check(withdrawal_amount) + }.must_raise ArgumentError end it "Allows 3 free uses" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 25.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + updated_balance = mulitple_check_withdrawal(3, account, withdrawal_amount) + + expected_balance = start_balance - withdrawal_amount * 3 + + account.balance.must_equal expected_balance + updated_balance.must_equal expected_balance end it "Applies a $2 fee after the third use" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 25.0 + fee = 2.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + updated_balance = mulitple_check_withdrawal(4, account, withdrawal_amount) + + expected_balance = start_balance - withdrawal_amount * 4 - fee + + account.balance.must_equal expected_balance + updated_balance.must_equal expected_balance + end + end + + # Calls withdraw_using_check num times + # returns updated_balance + def mulitple_check_withdrawal(num, account, withdrawal_amount) + updated_balance = nil + num.times do + updated_balance = account.withdraw_using_check(withdrawal_amount) end + updated_balance end describe "#reset_checks" do it "Can be called without error" do - # TODO: Your test code here! + account = Bank::CheckingAccount.new(12345, 100.0) + account.reset_checks end it "Makes the next three checks free if less than 3 checks had been used" do - # TODO: Your test code here! + start_balance = 200.0 + withdrawal_amount = 25.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + mulitple_check_withdrawal(2, account, withdrawal_amount) + account.reset_checks + mulitple_check_withdrawal(3, account, withdrawal_amount) + + expected_balance = 200.0 - withdrawal_amount * 5 + + account.balance.must_equal expected_balance end it "Makes the next three checks free if more than 3 checks had been used" do - # TODO: Your test code here! + start_balance = 200.0 + withdrawal_amount = 25.0 + fee = 2.0 + account = Bank::CheckingAccount.new(12345, start_balance) + + mulitple_check_withdrawal(4, account, withdrawal_amount) + account.reset_checks + mulitple_check_withdrawal(3, account, withdrawal_amount) + + expected_balance = 200.0 - withdrawal_amount * 7 - fee + + account.balance.must_equal expected_balance end end end diff --git a/specs/savings_account_spec.rb b/specs/savings_account_spec.rb index 3f4d1e4a..19f0df4b 100644 --- a/specs/savings_account_spec.rb +++ b/specs/savings_account_spec.rb @@ -1,17 +1,9 @@ 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 @@ -20,39 +12,94 @@ end it "Requires an initial balance of at least $10" do - # TODO: Your test code here! + proc { + Bank::SavingsAccount.new(12345, 9) + }.must_raise ArgumentError end end describe "#withdraw" do it "Applies a $2 fee each time" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 25.00 + fee = 2.0 + account = Bank::SavingsAccount.new(12345, start_balance) + + updated_balance = account.withdraw(withdrawal_amount) + expected_balance = start_balance - withdrawal_amount - fee + + # Both the value returned and the balance in the account + # should apply the fee + updated_balance.must_equal expected_balance + account.balance.must_equal expected_balance end it "Outputs a warning if the balance would go below $10" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 91.0 + account = Bank::SavingsAccount.new(12345, start_balance) + + # Anything printed to the console output will pass the test + proc { + account.withdraw(withdrawal_amount) + }.must_output (/.+/) end it "Doesn't modify the balance if it would go below $10" do - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 91.0 + account = Bank::SavingsAccount.new(12345, start_balance) + + updated_balance = account.withdraw(withdrawal_amount = 91.0) + + # 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 - # TODO: Your test code here! + start_balance = 100.0 + withdrawal_amount = 90.0 # + 2.0 fee + + account = Bank::SavingsAccount.new(12345, start_balance) + + updated_balance = account.withdraw(withdrawal_amount = 91.0) + + # 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 end describe "#add_interest" do it "Returns the interest calculated" do - # TODO: Your test code here! + account = Bank::SavingsAccount.new(12345, 10_000.0) + rate = 0.25 # percentage + interest = account.add_interest(rate) + expected_interest = 25.0 + + interest.must_equal expected_interest end it "Updates the balance with calculated interest" do - # TODO: Your test code here! + rate = 0.25 # percentage + account = Bank::SavingsAccount.new(12345, 10_000.0) + account.add_interest(rate) + + expected_balance = 10_025.0 + account.balance.must_equal expected_balance end it "Requires a positive rate" do - # TODO: Your test code here! + rate = -0.25 + account = Bank::SavingsAccount.new(12345, 10_000) + + proc { + account.add_interest(rate) + }.must_raise ArgumentError end end end