diff --git a/README.md b/README.md index 6251da8d..db6d04bf 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Make sure to write tests for any optionals you implement! - Add an `owner` property to each Account to track information about who owns the account. - The `Account` can be created with an `owner`, OR you can create a method that will add the `owner` after the `Account` has already been created. - - diff --git a/lib/account.rb b/lib/account.rb index e69de29b..a402c3f7 100644 --- a/lib/account.rb +++ b/lib/account.rb @@ -0,0 +1,52 @@ +require 'csv' + +module Bank + + class Account + attr_reader :id, :balance, :date + attr_accessor :owner + + def initialize(id, balance, date, owner = "Customer Name") + raise ArgumentError.new("You cannot create a bank account with a 0 or negative balance.") if balance < 0 + @id = id + @balance = balance + @date = date + @owner = owner + end + + def self.all + return @all_accounts if @all_accounts + @all_accounts = [] + CSV.open("/Users/brenna/ada/week3/BankAccounts/support/accounts.csv").each do | line | + @all_accounts << Bank::Account.new(line[0].to_i, line[1].to_f, line[2]) + end + @all_accounts + end + + def self.find(id) + self.all.each do |acct| + if acct.id == id + return acct + end + end + raise ArgumentError.new("There's no such account ID.") + end + + def withdraw(withdrawal_amount) + raise ArgumentError.new("You cannot withdraw a negative amount of money.") if withdrawal_amount < 0 + if withdrawal_amount > @balance + print "You can't overdraw your account." + else + return @balance -= withdrawal_amount + end + return @balance + end + + def deposit(deposit_amount) + raise ArgumentError.new("You cannot deposit a negative amount of money.") if deposit_amount < 0 + @balance += deposit_amount + end + + end + +end diff --git a/lib/checking.rb b/lib/checking.rb new file mode 100644 index 00000000..b6828017 --- /dev/null +++ b/lib/checking.rb @@ -0,0 +1,43 @@ + +module Bank + + class CheckingAccount < Account + + def initialize(id, balance, date, owner = "Customer Name") + super + @check_count = 0 + end + + def withdraw(withdrawal_amount) + if (withdrawal_amount + 1) > @balance + print "You can't overdraw your account." + return @balance + else + @balance -= (withdrawal_amount + 1) + end + return @balance + end + + def withdraw_using_check(withdrawal_amount) + raise ArgumentError.new("You cannot withdraw a negative amount of money using a check.") if withdrawal_amount < 0 + + if @balance < withdrawal_amount - 10 + print "There must always be at least $10 in your savings." + return @balance + end + + @check_count += 1 + if @check_count <= 3 + return @balance -= withdrawal_amount + else + return @balance -= (withdrawal_amount + 2) + end + end + + def reset_checks + @check_count = 0 + end + + end + +end diff --git a/lib/owner.rb b/lib/owner.rb new file mode 100644 index 00000000..30b7830a --- /dev/null +++ b/lib/owner.rb @@ -0,0 +1,48 @@ +require 'csv' + +module Bank + + class Owner + attr_reader :id, :last_name, :first_name, :st_address, :city, :state + + def initialize(owner_hash) + @id = owner_hash[:id] + @last_name = owner_hash[:last_name] + @first_name = owner_hash[:first_name] + @st_address = owner_hash[:st_address] + @city = owner_hash[:city] + @state = owner_hash[:state] + end + + def self.all + # pro tip from Jeremy! memoization: first time, @all_owners is nil, so it reads the CSV, but next time, it just returns @all_owners + return @all_owners if @all_owners + @all_owners = [] + CSV.open("/Users/brenna/ada/week3/BankAccounts/support/account_owners.csv").each do | line | + owner_hash = { + id: line[0].to_i, + last_name: line[1], + first_name: line[2], + st_address: line[3], + city: line[4], + state: line[5] + } + @all_owners << Bank::Owner.new(owner_hash) + end + + @all_owners + end + + def self.find(id) + self.all.each do |owner| + if owner.id == id + return owner + end + end + + raise ArgumentError.new("There's no such owner ID.") + end + + end + +end diff --git a/lib/savings.rb b/lib/savings.rb new file mode 100644 index 00000000..cdd40781 --- /dev/null +++ b/lib/savings.rb @@ -0,0 +1,30 @@ + +module Bank + + class SavingsAccount < Account + + attr_accessor :interest + + def initialize(id, balance, date, owner = "Customer Name") + super + raise ArgumentError.new("Balance must be over $10.") if balance < 10 + end + + def withdraw(withdrawal_amount) + if @balance < withdrawal_amount + 12 + print "There must always be at least $10 in your savings." + return @balance + end + super + @balance -= 2 + end + + def add_interest(rate) + raise ArgumentError.new("You can't calculate negative interest.") if rate < 0 + @interest = @balance * rate / 100 + @balance += @interest + end + + end + +end diff --git a/specs/account_spec.rb b/specs/account_spec.rb index 6c399139..976a999f 100644 --- a/specs/account_spec.rb +++ b/specs/account_spec.rb @@ -1,35 +1,45 @@ require 'minitest/autorun' require 'minitest/reporters' require 'minitest/skip_dsl' +require 'csv' require_relative '../lib/account' +require_relative '../lib/owner' describe "Wave 1" do + + before do + brenna_hash = {id: 0522, last_name: "Darroch", first_name: "Brenna", st_address: "3426 Cotton Top Ct", city: "Fairfax", state: "VA"} + @brenna = Bank::Owner.new(brenna_hash) + end # remember, end is garbage collector! + describe "Account#initialize" do - it "Takes an ID and an initial balance" do + it "Takes an ID, an initial balance, an open date, and an instance of Owner" do id = 1337 balance = 100.0 - account = Bank::Account.new(id, balance) + date = "Jan 1, 2001" + account = Bank::Account.new(id, balance, date, @brenna) account.must_respond_to :id account.id.must_equal id account.must_respond_to :balance account.balance.must_equal balance + + account.must_respond_to :date + account.date.must_equal date + + account.must_respond_to :owner + account.owner.must_equal @brenna 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) + Bank::Account.new(1337, -100, "Jan 1, 2001", @brenna) }.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) + Bank::Account.new(1337, 0, "Jan 1, 2001", @brenna) end end @@ -37,7 +47,7 @@ it "Reduces the balance" do start_balance = 100.0 withdrawal_amount = 25.0 - account = Bank::Account.new(1337, start_balance) + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) account.withdraw(withdrawal_amount) @@ -48,7 +58,7 @@ it "Returns the modified balance" do start_balance = 100.0 withdrawal_amount = 25.0 - account = Bank::Account.new(1337, start_balance) + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) updated_balance = account.withdraw(withdrawal_amount) @@ -59,12 +69,7 @@ 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) - - # 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. + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) proc { account.withdraw(withdrawal_amount) }.must_output /.+/ @@ -73,18 +78,16 @@ it "Doesn't modify the balance 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, "Jan 1, 2001", @brenna) 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 "Allows the balance to go to 0" do - account = Bank::Account.new(1337, 100.0) + start_balance = 100 + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) updated_balance = account.withdraw(account.balance) updated_balance.must_equal 0 account.balance.must_equal 0 @@ -93,7 +96,7 @@ it "Requires a positive withdrawal amount" do start_balance = 100.0 withdrawal_amount = -25.0 - account = Bank::Account.new(1337, start_balance) + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) proc { account.withdraw(withdrawal_amount) @@ -105,7 +108,7 @@ it "Increases the balance" do start_balance = 100.0 deposit_amount = 25.0 - account = Bank::Account.new(1337, start_balance) + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) account.deposit(deposit_amount) @@ -116,7 +119,7 @@ it "Returns the modified balance" do start_balance = 100.0 deposit_amount = 25.0 - account = Bank::Account.new(1337, start_balance) + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) updated_balance = account.deposit(deposit_amount) @@ -127,45 +130,83 @@ it "Requires a positive deposit amount" do start_balance = 100.0 deposit_amount = -25.0 - account = Bank::Account.new(1337, start_balance) + account = Bank::Account.new(1337, start_balance, "Jan 1, 2001", @brenna) proc { account.deposit(deposit_amount) }.must_raise ArgumentError end + end end -# TODO: change 'xdescribe' to 'describe' to run these tests -xdescribe "Wave 2" do +describe "Wave 2" do + # to make test adaptable (eg. calling diff account files), need to read in CSV file to spec file too + + before do + @accounts = Bank::Account.all + end + 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 + @accounts.must_be_kind_of Array + end + + it "Everything in the array is an Account" do + @accounts.each do |inst| + inst.must_be_instance_of Bank::Account + end + end + + it "The number of accounts is correct" do + @accounts.length.must_equal 12 + end + + it "The ID and balance of the first and last accounts match what's in the CSV file" do + @accounts[0].id.must_equal 1212 + @accounts[0].balance.must_equal 1235667 + + @accounts[-1].id.must_equal 15156 + @accounts[-1].balance.must_equal 4356772 + end + + it "The elements match what's in the file" do + index = 0 + CSV.read("support/accounts.csv") do |line| + @accounts[index].id.must_equal line[0].to_i + @accounts[index].balance.must_equal line[1].to_f + @accounts[index].date.must_equal line[2] + index += 1 + end end end describe "Account.find" do + it "Returns an account that exists" do - # TODO: Your test code here! + id_check = @accounts[4].id + account = Bank::Account.find(id_check) + expect(account.id).must_equal id_check end it "Can find the first account from the CSV" do - # TODO: Your test code here! + id_check = @accounts[0].id + account = Bank::Account.find(id_check) + expect(account.id).must_equal id_check end it "Can find the last account from the CSV" do - # TODO: Your test code here! + id_check = @accounts[-1].id + account = Bank::Account.find(id_check) + expect(account.id).must_equal id_check end it "Raises an error for an account that doesn't exist" do - # TODO: Your test code here! + proc { + Bank::Account.find(12345) + }.must_raise ArgumentError end + end end diff --git a/specs/checking_account_spec.rb b/specs/checking_account_spec.rb index 7f95339e..fe08d172 100644 --- a/specs/checking_account_spec.rb +++ b/specs/checking_account_spec.rb @@ -2,79 +2,94 @@ require 'minitest/reporters' require 'minitest/skip_dsl' -# TODO: uncomment the next line once you start wave 3 and add lib/checking_account.rb -# require_relative '../lib/checking_account' +require_relative '../lib/checking' -# 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. +describe "CheckingAccount" do + + before do + @account = Bank::CheckingAccount.new(12345, 100.0, "Jan 1, 2017") + end -# TODO: change 'xdescribe' to 'describe' to run these tests -xdescribe "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.must_be_kind_of Bank::Account + @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! + @account.withdraw(50) + @account.balance.must_equal 49.0 end it "Doesn't modify the balance if the fee would put it negative" do - # TODO: Your test code here! + starting_balance = @account.balance + updated_balance = @account.withdraw(100) + updated_balance.must_equal starting_balance end end describe "#withdraw_using_check" do it "Reduces the balance" do - # TODO: Your test code here! + @account.withdraw_using_check(40).must_equal 100 - 40 end it "Returns the modified balance" do - # TODO: Your test code here! + @account.withdraw_using_check(40).must_equal 60 end it "Allows the balance to go down to -$10" do - # TODO: Your test code here! + @account.withdraw_using_check(110).must_equal (-10) end it "Outputs a warning if the account would go below -$10" do - # TODO: Your test code here! + proc { + @account.withdraw(150) + }.must_output /.+/ end it "Doesn't modify the balance if the account would go below -$10" do - # TODO: Your test code here! + starting_balance = @account.balance + updated_balance = @account.withdraw_using_check(150) + updated_balance.must_equal starting_balance end it "Requires a positive withdrawal amount" do - # TODO: Your test code here! + proc { + @account.withdraw_using_check(-25) + }.must_raise ArgumentError end it "Allows 3 free uses" do - # TODO: Your test code here! + 3.times { @account.withdraw_using_check(10) } + @account.balance.must_equal 70 end it "Applies a $2 fee after the third use" do - # TODO: Your test code here! + 4.times { @account.withdraw_using_check(10) } + @account.balance.must_equal 58 end end describe "#reset_checks" do it "Can be called without error" do - # TODO: Your test code here! + @account.must_respond_to :reset_checks end it "Makes the next three checks free if less than 3 checks had been used" do - # TODO: Your test code here! + 2.times { @account.withdraw_using_check(10) } + @account.balance.must_equal 80 + @account.reset_checks + 3.times { @account.withdraw_using_check(10) } + @account.balance.must_equal 50 end it "Makes the next three checks free if more than 3 checks had been used" do - # TODO: Your test code here! + 4.times { @account.withdraw_using_check(10) } + @account.balance.must_equal 58 + @account.reset_checks + @account.withdraw_using_check(10) + @account.balance.must_equal 48 end end end diff --git a/specs/owner_spec.rb b/specs/owner_spec.rb new file mode 100644 index 00000000..6927fa97 --- /dev/null +++ b/specs/owner_spec.rb @@ -0,0 +1,87 @@ +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/skip_dsl' +require 'csv' +require_relative '../lib/account' +require_relative '../lib/owner' + +describe "Wave 1 Optional" do + + before do + brenna_hash = {id: 0522, last_name: "Darroch", first_name: "Brenna", st_address: "3426 Cotton Top Ct", city: "Fairfax", state: "VA"} + @brenna = Bank::Owner.new(brenna_hash) + @account = Bank::Account.new(1234, 333, "May 22, 2011", @brenna) + @owners = Bank::Owner.all + end + + describe "Owner#initialize" do + it "Takes a hash with data on ID, first and last name, street address, city, and state." do + @account.owner.id.must_equal 0522 + @account.owner.last_name.must_equal "Darroch" + @account.owner.first_name.must_equal "Brenna" + @account.owner.st_address.must_equal "3426 Cotton Top Ct" + @account.owner.city.must_equal "Fairfax" + @account.owner.state.must_equal "VA" + end + end + + describe "Owner.all" do + + it "Returns an array of all owners" do + @owners.must_be_kind_of Array + end + + it "Everything in the array is an Owner" do + @owners.each do |inst| + inst.must_be_instance_of Bank::Owner + end + end + + it "The number of owners is correct" do + @owners.length.must_equal 12 + end + + it "The elements match what's in the file" do + index = 0 + CSV.read("support/owners.csv") do |line| + @owners[index].id.must_equal line[0].to_i + @owners[index].last_name.must_equal line[1] + @owners[index].last_name.must_equal line[2] + @owners[index].st_address.must_equal line[3] + @owners[index].city.must_equal line[4] + @owners[index].state.must_equal line[5] + index += 1 + end + end + end + + describe "Owner.find" do + + it "Returns an account that exists" do + id_check = @owners[4].id + owner = Bank::Owner.find(id_check) + expect(owner.id).must_equal id_check + end + + it "Can find the first account from the CSV" do + id_check = @owners[0].id + owner = Bank::Owner.find(id_check) + expect(owner.id).must_equal id_check + end + + it "Can find the last account from the CSV" do + id_check = @owners[-1].id + owner = Bank::Owner.find(id_check) + expect(owner.id).must_equal id_check + end + + it "Raises an error for an account that doesn't exist" do + proc { + Bank::Owner.find(12345) + }.must_raise ArgumentError + end + + end + + +end diff --git a/specs/savings_account_spec.rb b/specs/savings_account_spec.rb index 3f4d1e4a..fc12c45a 100644 --- a/specs/savings_account_spec.rb +++ b/specs/savings_account_spec.rb @@ -2,57 +2,67 @@ require 'minitest/reporters' require 'minitest/skip_dsl' -# TODO: uncomment the next line once you start wave 3 and add lib/savings_account.rb -# require_relative '../lib/savings_account' +require_relative '../lib/savings' -# 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. +describe "SavingsAccount" do + + before do + @account = Bank::SavingsAccount.new(12345, 100.0, "Jan 1, 2017") + end -# TODO: change 'xdescribe' to 'describe' to run these tests -xdescribe "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.must_be_kind_of Bank::Account + @account.must_be_kind_of Bank::Account end - it "Requires an initial balance of at least $10" do - # TODO: Your test code here! + it "Raises an error if initial balance is less than $10" do + proc { + Bank::SavingsAccount.new(12345, 5, "Jan 1, 2017") + }.must_raise ArgumentError end end describe "#withdraw" do it "Applies a $2 fee each time" do - # TODO: Your test code here! + @account.withdraw(50) + @account.balance.must_equal 48.0 end it "Outputs a warning if the balance would go below $10" do - # TODO: Your test code here! + proc { + @account.withdraw(150) + }.must_output /.+/ end it "Doesn't modify the balance if it would go below $10" do - # TODO: Your test code here! + starting_balance = @account.balance + updated_balance = @account.withdraw(150) + updated_balance.must_equal starting_balance end it "Doesn't modify the balance if the fee would put it below $10" do - # TODO: Your test code here! + starting_balance = @account.balance + updated_balance = @account.withdraw(90) + updated_balance.must_equal starting_balance end end describe "#add_interest" do it "Returns the interest calculated" do - # TODO: Your test code here! + rich_account = Bank::SavingsAccount.new(23456, 10000, "Jan 1, 2017") + rich_account.add_interest(0.25) + rich_account.interest.must_equal 25.0 end it "Updates the balance with calculated interest" do - # TODO: Your test code here! + rich_account = Bank::SavingsAccount.new(23456, 10000, "Jan 1, 2017") + rich_account.add_interest(0.25).must_equal 10025.0 end - it "Requires a positive rate" do - # TODO: Your test code here! + it "Raises an error if a negative rate is given" do + proc { + @account.add_interest(-0.5) + }.must_raise ArgumentError end end end