diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..deb52f2cd --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = true + t.test_files = FileList['specs/*_spec.rb'] +end + +task default: :test diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..fcd4ea4b1 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,34 @@ +1. What classes does each implementation include? Are the lists the same? + Each implementation has a CartEntry, ShoppingCart, and Order class. + +2. Write down a sentence to describe each class. + The CartEntry class is responsible for the individual item(s) inside the shopping cart. + + The ShoppingCart class is responsible for all of the items in the shopping cart (instances of CartEntry). + + The Order class is responsible for one instance of a shopping trip (shopping cart). + +3. How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + The Order class contains one instance of the ShoppingCart class. The ShoppingCart class contains an array of CartEntry class instances. + +4. What data does each class store? How (if at all) does this differ between the two implementations? + For both implementations, the CartEntry class contains the unit price and quantity of a cart item. The ShoppingCart class contains an array of CartEntry instances. The Order class contains an instance of the ShoppingCart class and a Sales_Tax constant variable. + +5. What methods does each class have? How (if at all) does this differ between the two implementations? + All of the classes have initialize methods. In implementation A, the Order class has a total_price method. In implementation B, all three classes have price methods. + +6. Consider the Order#total_price method. In each implementation: + a. Is logic to compute the price delegated to "lower level" classes like ShoppingCart and CartEntry, or is it retained in Order? + In implementation A, the logic to compute price is retained in the Order class. In implementation B, the logic to compute price is delegated to lower level classes. + + b. Does total_price directly manipulate the instance variables of other classes? + In implementation A, the total_price method does manipulate instance variables of other classes. In implementation B, the total_price method does not directly manipulate instance variables. The instance variables of other classes are manipulated through class methods. + +7. If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + In order to implement this change, a new method would have to be added to the CartEntry class to contain the logic for changing the price based on quantity. It would be easier to change implementation B because it already contains a price method. + +8. Which implementation better adheres to the single responsibility principle? + Implementation B better adheres to the single responsibility principle because the logic of calculating the total cost is passed off to "lower level" classes. The Order class, in implementation B, calls the methods of other classes instead of directly manipulating instance variables. + +9. Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + Implementation B is more loosely coupled because each of the classes have less dependency. In implementation A, the data in the CartEntry and ShoppingCart classes are not useful without the Order class. But in implementation B, the CartEntry and ShoppingCart classes do not necessarily need the Order class. They are still dependent on each other but not as dependent as in implementation A. diff --git a/hotel_cli.rb b/hotel_cli.rb new file mode 100644 index 000000000..c07e2ad95 --- /dev/null +++ b/hotel_cli.rb @@ -0,0 +1,171 @@ +require 'pry' + +require_relative 'lib/admin' +require_relative 'lib/reservation' +require_relative 'lib/room' + +HOTEL = Hotel::Admin.new + +def display_options + puts + puts "----------------- OPTIONS ----------------" + puts "SHOW all reservations - reservations OR 1" + puts "ADD reservation - add reservation OR 2" + puts "SHOW all blocks - blocks OR 3" + puts "ADD block - add block OR 4" + puts "To exit this program - exit" + puts "------------------------------------------" + puts "OPTION: " + puts +end + +def show_rooms(rooms) + i = 0 + while i < rooms.length + print "\nRoom ID: #{rooms[i].room_id} | " + print "Reserved?: #{rooms[i].is_reserved.to_s} | " + print "In block?: #{rooms[i].is_in_block.to_s}" + i += 1 + end + puts +end + +def show_reservations(reservations) + if reservations.length == 0 + puts "\nThere are no current reservations." + else + i = 0 + while i < reservations.length + print "\nRoom ID: #{reservations[i].room.room_id} | " + print "Start Date: #{reservations[i].start_date} | " + print "End Date: #{reservations[i].end_date} | " + print "Cost per night: #{reservations[i].cost_per_night} | " + print "Total Cost: #{reservations[i].total_cost}" + i += 1 + end + puts + end +end + +def valid_input_loop(pattern) + input = gets.chomp + while !pattern.match(input) + puts "Please enter info in the valid format." + input = gets.chomp + end + return input +end + +def get_date_range + pattern = /^\d{4}\-\d{2}\-\d{2}$/ + valid_date = false + until valid_date == true + date_range = {} + puts "Please enter start date in the following format -> yyyy-mm-dd: " + start_date_input = valid_input_loop(pattern) + start_date = Date.strptime( start_date_input, '%Y-%m-%d') + date_range[:start_date] = start_date + puts "Please enter end date in the following format -> yyyy-mm-dd: " + end_date_input = valid_input_loop(pattern) + end_date = Date.strptime( end_date_input, '%Y-%m-%d') + date_range[:end_date] = end_date + if HOTEL.check_date_range(date_range) + valid_date = true + else + puts "Invalid date range - try again" + end + end + return date_range +end + +def show_available_rooms(date_range) + puts "\nThe following rooms are available for reservation: " + available_rooms = HOTEL.get_unreserved_rooms(date_range) + available_rooms.each do |room| + print "#{room} | " + end + puts + return available_rooms +end + +def show_reservation(reservation) + puts " - Start Date: #{reservation.start_date}" + puts " - End Date: #{reservation.end_date}" + puts " - Room ID: #{reservation.room.room_id}" + puts +end + +def get_reservation + date_range = get_date_range + available_rooms = show_available_rooms(date_range) + + puts "\nPlease enter an available room ID" + pattern = /^[1-9]{1}[0-9]{0,1}$/ + room_id = valid_input_loop(pattern).to_i + until room_id <= HOTEL.num_rooms && room_id > 0 && available_rooms.include?(room_id) + puts "\nInvalid room ID" + show_available_rooms(date_range) + room_id = valid_input_loop(pattern).to_i + end + + return HOTEL.add_reservation(date_range, room_id) +end + +def show_block(block) + puts " - Start Date: #{block[:start_date]}" + puts " - End Date: #{block[:end_date]}" + puts " - Rooms: " + rooms = block[:rooms] + rooms.each do |room| + print "#{room.room_id} | " + end + puts " - Cost/Night: $#{block[:room_rate]}" +end + +def get_block + date_range = get_date_range + # available_rooms = show_available_rooms(date_range) + puts "\nPlease enter # of rooms in block [1-5]." + pattern = /^[1-5]{1}/ + num_rooms = valid_input_loop(pattern).to_i + return HOTEL.make_block(date_range, num_rooms) +end + +puts "Welcome to [insert name here] Hotel!" +puts "\n--------------- HOTEL INFO ---------------" +puts "Number of rooms: #{HOTEL.num_rooms}" +puts "Base room rate: #{HOTEL.base_rate}" +puts "Discount rate/room for block reservation: #{HOTEL.discount_rate * 100}%" +puts + +display_options +command = gets.chomp + +while command != "exit" + if command == "reservations" || command == "1" + show_reservations(HOTEL.reservations) + elsif command == "add reservation" || command == "2" + reservation = get_reservation + puts "\nReservation Complete!" + show_reservation(reservation) + elsif command == "blocks" || command == "3" + if HOTEL.blocks.length == 0 + puts "There are no blocks" + else + HOTEL.blocks.each do |block| + show_block(block) + puts + end + end + puts + elsif command == "add block" || command == "4" + block = get_block + puts "\nBlock Complete!" + show_block(block) + else + puts "Please enter a valid command." + end + puts + display_options + command = gets.chomp +end diff --git a/lib/.keep b/lib/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/admin.rb b/lib/admin.rb new file mode 100644 index 000000000..fb348c595 --- /dev/null +++ b/lib/admin.rb @@ -0,0 +1,236 @@ +require_relative 'reservation' +require_relative 'room' + +require 'date' + +module Hotel + + class Admin + + attr_reader :reservations, :num_rooms, :rooms, :base_rate, :discount_rate, :blocks + + def initialize + @reservations = [] + @blocks = [] + @num_rooms = 20 + @rooms = create_rooms_array + @base_rate = 200 + @discount_rate = 0.05 + end + +#---------------------------------------------------------------------# + + def create_rooms_array + rooms = [] + is_reserved = false + num_rooms.times do |i| + id = i + 1 + rooms << create_room_instance(id, is_reserved) + end + return rooms + end + + def create_room_instance(id, is_reserved) + if id.class == Integer + return Hotel::Room.new(id, is_reserved) + else + raise ArgumentError.new("ID is invalid") + end + end + +#---------------------------------------------------------------------# + + def make_block(date_range, num_rooms_in_block) + if num_rooms_in_block < 2 || num_rooms_in_block > 5 + raise StandardError.new("Blocks can only contain 2-5 rooms") + end + + unreserved_room_ids = get_unreserved_rooms(date_range) + if unreserved_room_ids.length >= num_rooms_in_block + + room_objs = get_room_objs(unreserved_room_ids.shift(num_rooms_in_block)) + room_objs.each do |room| + room.is_in_block = room.in_block + end + + block = { + start_date: date_range[:start_date], + end_date: date_range[:end_date], + rooms: room_objs, + room_rate: cost_per_night(num_rooms_in_block) + } + + blocks << block + return block + else + return nil + end + end + + def add_reservation_in_block(room_id) + + if room_id.class != Integer || room_id <= 0 || room_id > num_rooms + raise ArgumentError.new("Invalid room ID") + end + block = get_block(room_id) + if block != {} + date_range = {} + date_range[:start_date] = block[:start_date] + date_range[:end_date] = block[:end_date] + room = get_room(room_id) + room.is_reserved = room.reserved + cost = block[:room_rate] + + new_reservation = Hotel::Reservation.new(date_range, room, cost_per_night: cost) + reservations << new_reservation + else + raise StandardError.new("That room is not in a block.") + end + + end + + def add_reservation(date_range, room_id) + if check_date_range(date_range) && get_unreserved_rooms(date_range).include?(room_id) + selected_room = get_room(room_id) + selected_room.is_reserved = true + new_reservation = Hotel::Reservation.new(date_range, selected_room) + reservations << new_reservation + return new_reservation + else + raise StandardError.new("Date range OR room ID is invalid") + end + end + + def check_date_range(date_range) + start_time = date_range[:start_date].to_time + end_time = date_range[:end_date].to_time + if end_time - start_time < 0 + return false + else + return true + end + end + +#---------------------------------------------------------------------# + + #TODO: add error if date is not a date object + def get_reservation_list(date) + if reservations.length != 0 + reservation_list = reservations.select { |reservation| compare_dates(reservation, date) } + return reservation_list + else + return [] + end + end + + def compare_dates(reservation, date) + start_date = reservation.start_date + end_date = reservation.end_date + # date is w/i the reservation date_range + if ((date <=> start_date) == 1) && ((end_date <=> date) == 1) + return true + # date is NOT w/i the reservation date_range ( + elsif ((date <=> start_date) == -1) || ((end_date <=> date) == -1) + return false + # date is either equal to the start_date or to the end_date (TRUE) + elsif ((date <=> start_date) == 0) || ((end_date <=> date) == 0) + return true + end + end + +#---------------------------------------------------------------------# + + #NOTE: will not return rooms in blocks. + def get_unreserved_rooms(date_range) + unreserved_room_ids = check_reservations(date_range, reservations) + + blocks.each do |block| + block_start = block[:start_date] + block_end = block[:end_date] + room_objs = block[:rooms] + room_ids = room_objs.map {|room| room.room_id} + + if date_range[:start_date] >= block_end == true + next + else + if date_range[:end_date] <= block_start == false + room_ids.each do |id| + unreserved_room_ids.delete(id) + end + end + end + end + + return unreserved_room_ids.sort.uniq + end + +#---------------------------------------------------------------------# + + def check_reservations(date_range, reservations) + desired_start_date = date_range[:start_date] + desired_end_date = date_range[:end_date] + + unreserved_room_ids = (1..@num_rooms).to_a + @reservations.each do |reservation| + reservation_start = reservation.start_date + reservation_end = reservation.end_date + if desired_start_date >= reservation_end == true + next + else + if desired_end_date <= reservation_start == false + unreserved_room_ids.delete(reservation.room.room_id) + end + end + end + + return unreserved_room_ids + end + + def check_block(block) + unreserved_room_ids = [] + rooms = block[:rooms] + rooms.each do |room| + if room.is_reserved == false + unreserved_room_ids << room.room_id + end + end + return unreserved_room_ids + end + + def get_block(room_id) + block = {} + + blocks.each do |b| + rooms = b[:rooms] + rooms.each do |r| + if r.room_id == room_id + block = b + break + end + end + end + return block + end + + private + + def cost_per_night(num_rooms) + return base_rate - (base_rate*(discount_rate * num_rooms)) + end + + def get_room(id) + return rooms.select {|room| room.room_id == id}.first + end + + def get_room_objs(room_ids) + room_objs = [] + i = 0 + while i < room_ids.length + room_objs << get_room(room_ids[i]) + i += 1 + end + return room_objs + end + + end +end #end of module diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..54223057a --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,30 @@ +module Hotel + class Reservation + + attr_reader :start_date, :end_date, :cost_per_night, :total_cost, :room + + def initialize(date_range, room, cost_per_night: 200) + @start_date = date_range[:start_date] + @end_date = date_range[:end_date] + # @cost_per_night = 200 + @cost_per_night = cost_per_night + @total_cost = calculate_cost + @room = room + end + + def calculate_cost + total_days = (end_date - start_date).to_i + + if total_days == 0 + raise StandardError.new("Start date cannot equal end date.") + elsif total_days == 1 + cost = cost_per_night + else + cost = (total_days - 1) * cost_per_night + end + + return cost + end + + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..d2f15bcf8 --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,22 @@ +module Hotel + class Room + + attr_reader :room_id + attr_accessor :is_reserved, :is_in_block + + def initialize(id, is_reserved, is_in_block: false) + @room_id = id + @is_reserved = is_reserved + @is_in_block = is_in_block + end + + def in_block + return @is_in_block == true ? false : true + end + + def reserved + return @is_reserved == true ? false : true + end + + end +end diff --git a/specs/.keep b/specs/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/specs/admin_spec.rb b/specs/admin_spec.rb new file mode 100644 index 000000000..8e1396395 --- /dev/null +++ b/specs/admin_spec.rb @@ -0,0 +1,529 @@ +require_relative 'spec_helper' +require 'date' +require 'pry' + +describe "Admin Class" do + before do + @admin = Hotel::Admin.new + end + + describe "Initializer" do + it "is an instance of Admin" do + @admin.must_be_instance_of Hotel::Admin + end + + it "creates initial data structures" do + @admin.reservations.must_be_kind_of Array + @admin.blocks.must_be_kind_of Array + @admin.num_rooms.must_be_kind_of Integer + @admin.rooms.must_be_kind_of Array + @admin.base_rate.must_be_kind_of Integer + @admin.discount_rate.must_be_kind_of Float + end + end + + describe "#create_rooms" do + before do + @initial_rooms = @admin.create_rooms_array + end + + it "returns an array of Room instances" do + @initial_rooms.must_be_kind_of Array + @initial_rooms.all? {|room| room.must_be_instance_of Hotel::Room} + end + + it "assigns correct ids to Room instances" do + @initial_rooms.first.room_id.must_equal 1 + @initial_rooms.last.room_id.must_equal 20 + end + end + + describe "#create_room_instance" do + before do + @id = 1 + @bad_id = 'id' + @is_reserved = false + end + + it "returns a Room instance" do + room = @admin.create_room_instance(@id, @is_reserved) + room.must_be_instance_of Hotel::Room + end + + it "raises an ArgumentError if id is not an Integer" do + proc { + @admin.create_room_instance(@bad_id, @is_reserved) + }.must_raise ArgumentError + end + end + + describe "#make_block" do + before do + @date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + end + + it "returns a hash" do + num_rooms = 2 + block = @admin.make_block(@date_range, num_rooms) + block.must_be_kind_of Hash + end + + it "raises a StandardError if num_rooms < 2" do + num_rooms = 1 + proc { + @admin.make_block(@date_range, num_rooms) + }.must_raise StandardError + end + + it "raises a StandardError if num_rooms > 5" do + num_rooms = 6 + proc { + @admin.make_block(@date_range, num_rooms) + }.must_raise StandardError + end + + it "returns nil if there are not enough rooms available" do + @admin.num_rooms.times do |num| + @admin.add_reservation(@date_range, num + 1) + end + num_rooms = 2 + block = @admin.make_block(@date_range, num_rooms) + block.must_equal nil + end + + it "returns the correct block" do + num_rooms = 5 + block = @admin.make_block(@date_range, num_rooms) + block[:start_date].must_equal Date.new(2018, 03, 05) + block[:end_date].must_equal Date.new(2018, 03, 10) + block[:rooms].must_be_kind_of Array + block[:rooms].length.must_equal 5 + block[:room_rate].must_equal 150 + end + + it "returns a block with an Array of Room objs" do + num_rooms = 5 + block = @admin.make_block(@date_range, num_rooms) + rooms = block[:rooms] + rooms.all? {|room| room.must_be_instance_of Hotel::Room} + rooms.all? {|room| room.is_in_block.must_equal true} + end + end + + describe "#add_reservation_in_block" do + before do + @date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + end + + it "raises a StandardError if room with room_id is not in a block" do + room_id = 4 + @admin.add_reservation(@date_range, room_id) + proc { + @admin.add_reservation_in_block(room_id) + }.must_raise StandardError + end + + it "raises a ArgumentError if room_id is not valid integer" do + room_id = "4" + room_id2 = 55 + proc { + @admin.add_reservation_in_block(room_id) + @admin.add_reservation_in_block(room_id2) + }.must_raise ArgumentError + end + + it "successfully creates new reservation for valid room in block" do + num_rooms = 2 + + block = @admin.make_block(@date_range, num_rooms) + rooms = block[:rooms] + first_room = rooms.first + + first_room.is_reserved.must_equal false + @admin.add_reservation_in_block(1) + rooms = block[:rooms] + rooms.first.is_in_block.must_equal true + rooms.first.is_reserved.must_equal true + end + + end + + describe "#add_reservation" do + before do + @date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + + @bad_date_range = { + start_date: Date.new(2018, 05, 05), + end_date: Date.new(2018, 01, 01) + } + + @room_id = 1 + end + + it "raises a StandardError for invalid date range" do + proc { + @admin.add_reservation(@bad_date_range, @room_id) + }.must_raise StandardError + end + + it "adds new reservation to reservations array" do + initial_length = @admin.reservations.length + @admin.add_reservation(@date_range, @room_id) + new_length = @admin.reservations.length + + new_length.must_equal initial_length + 1 + end + + it "raises a StandardError if room is unavailable" do + room_id = 1 + @admin.add_reservation(@date_range, room_id) + proc { + @admin.add_reservation(@date_range, room_id) + }.must_raise StandardError + end + end + + describe "#check_date_range" do + before do + @date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + + @bad_date_range = { + start_date: Date.new(2018, 05, 05), + end_date: Date.new(2018, 01, 01) + } + + @one_day = { + start_date: Date.new(2018, 05, 05), + end_date: Date.new(2018, 05, 05) + } + end + + it "returns true for a valid date range" do + reservation = @admin.check_date_range(@date_range) + reservation.must_equal true + end + + it "returns false for an invalid date range" do + reservation = @admin.check_date_range(@bad_date_range) + reservation.must_equal false + end + + it "returns true if start_date == end_date" do + reservation = @admin.check_date_range(@one_day) + reservation.must_equal true + end + end + + describe "#get_reservation_list(date)" do + it "returns an array" do + date = Date.new(2018, 03, 05) + list = @admin.get_reservation_list(date) + list.must_be_kind_of Array + end + + it "returns an empty array if there are no reservations" do + date = Date.new(2018, 04, 05) + @admin.get_reservation_list(date).must_equal [] + end + + it "returns one reservation if date is end_date of a reservation" do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + room_id = 1 + @admin.add_reservation(date_range, room_id) + list = @admin.get_reservation_list(Date.new(2018, 03, 10)) + + list.length.must_equal 1 + list.first.start_date.must_equal Date.new(2018, 03, 05) + list.last.end_date.must_equal Date.new(2018, 03, 10) + end + + it "returns one reservation if date is start_date of a reservation" do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + room_id = 1 + @admin.add_reservation(date_range, room_id) + list = @admin.get_reservation_list(Date.new(2018, 03, 05)) + + list.length.must_equal 1 + list.first.start_date.must_equal Date.new(2018, 03, 05) + list.last.end_date.must_equal Date.new(2018, 03, 10) + end + + it "returns one reservation if date is in between start_date and end_date" do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + room_id = 1 + @admin.add_reservation(date_range, room_id) + list = @admin.get_reservation_list(Date.new(2018, 03, 06)) + + list.length.must_equal 1 + list.first.start_date.must_equal Date.new(2018, 03, 05) + list.last.end_date.must_equal Date.new(2018, 03, 10) + end + + it "returns empty array if date is not in any reservations" do + date_range1 = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + date_range2 = { + start_date: Date.new(2017, 07, 05), + end_date: Date.new(2017, 07, 23) + } + room_id = 1 + @admin.add_reservation(date_range1, room_id) + @admin.add_reservation(date_range2, room_id) + + list = @admin.get_reservation_list(Date.new(2018, 03, 11)) + list.must_equal [] + end + end + + describe "#compare_dates(reservation, date)" do + before do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + room_id = 1 + @admin.add_reservation(date_range, room_id) + @reservation = @admin.reservations.first + end + + it "returns true if date is w/i date range of the reservation" do + date = Date.new(2018, 03, 07) + compare = @admin.compare_dates(@reservation, date) + compare.must_equal true + end + + it "returns true if date == start_date of a reservation" do + date = Date.new(2018, 03, 05) + compare = @admin.compare_dates(@reservation, date) + compare.must_equal true + end + + it "returns true if date == end_date of a reservation" do + date = Date.new(2018, 03, 10) + compare = @admin.compare_dates(@reservation, date) + compare.must_equal true + end + + it "returns false if date is NOT w/i date range of the reservation" do + date = Date.new(2018, 04, 01) + compare = @admin.compare_dates(@reservation, date) + compare.must_equal false + end + end + + describe "#get_unreserved_rooms(date_range)" do + before do + @date_range1 = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + end + + it "returns an Array of Integers (Room Ids)" do + @admin.get_unreserved_rooms(@date_range1).must_be_kind_of Array + @admin.get_unreserved_rooms(@date_range1).all? { + |room| room.must_be_kind_of Integer + } + end + + it "returns an empty Array if there are no available rooms" do + @admin.num_rooms.times do |num| + @admin.add_reservation(@date_range1, num + 1) + end + + available_rooms = @admin.get_unreserved_rooms(@date_range1) + available_rooms.must_equal [] + end + + it "returns all room ids when start_date of new reservation == end date of one previous reservation" do + room_id = 1 + @admin.add_reservation(@date_range1, room_id) + + date_range = { + start_date: Date.new(2018, 03, 10), + end_date: Date.new(2018, 03, 12) + } + + available_rooms = @admin.get_unreserved_rooms(date_range) + available_rooms.must_equal (1..20).to_a + end + + it "returns 19 room ids when date range is completely within an established reservation range" do + room_id = 1 + @admin.add_reservation(@date_range1, room_id) + + date_range = { + start_date: Date.new(2018, 03, 06), + end_date: Date.new(2018, 03, 9) + } + + available_rooms = @admin.get_unreserved_rooms(date_range) + available_rooms.must_equal (2..20).to_a + end + + it "returns 19 room ids when date range is exactly the same as an established reservation range" do + room_id = 1 + @admin.add_reservation(@date_range1, room_id) + + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + + available_rooms = @admin.get_unreserved_rooms(date_range) + available_rooms.must_equal (2..20).to_a + end + + it "returns 20 room ids when date range is completely outside of an established reservation range" do + room_id = 1 + @admin.add_reservation(@date_range1, room_id) + + date_range = { + start_date: Date.new(2018, 03, 12), + end_date: Date.new(2018, 03, 15) + } + + available_rooms = @admin.get_unreserved_rooms(date_range) + available_rooms.must_equal (1..20).to_a + end + + it "returns 19 room ids when date range overlaps an established reservation range once" do + room_id = 1 + @admin.add_reservation(@date_range1, room_id) + + date_range = { + start_date: Date.new(2018, 03, 4), + end_date: Date.new(2018, 03, 9) + } + + available_rooms = @admin.get_unreserved_rooms(date_range) + available_rooms.must_equal (2..20).to_a + end + + it "returns 19 room ids when date range overlaps an established reservation range twice" do + room_id = 1 + @admin.add_reservation(@date_range1, room_id) + + date_range = { + start_date: Date.new(2018, 03, 4), + end_date: Date.new(2018, 03, 12) + } + + available_rooms = @admin.get_unreserved_rooms(date_range) + available_rooms.must_equal (2..20).to_a + end + + it "does not return rooms within a block" do + num_rooms = 4 + block = @admin.make_block(@date_range1, num_rooms) + + unreserved_room_ids = @admin.get_unreserved_rooms(@date_range1) + rooms = block[:rooms] + block_room_ids = rooms.map {|room| room.room_id} + + unreserved_room_ids.each do |id| + block_room_ids.include?(id).must_equal false + end + end + + it "returns rooms within a block if start_date == end_date of block" do + num_rooms = 2 + date_range = { + start_date: Date.new(2018, 03, 4), + end_date: Date.new(2018, 03, 12) + } + block = @admin.make_block(date_range, num_rooms) + + new_date_range = { + start_date: Date.new(2018, 03, 12), + end_date: Date.new(2018, 03, 15) + } + + room_ids = @admin.get_unreserved_rooms(new_date_range) + room_ids.include?(1).must_equal true + room_ids.include?(2).must_equal true + end + end + + describe "#check_reservations" do + it "returns an array" do + date_range = { + start_date: Date.new(2018, 03, 5), + end_date: Date.new(2018, 03, 10) + } + reservations = @admin.reservations + @admin.check_reservations(date_range, reservations).must_be_kind_of Array + end + end + + describe "#check_block(block)" do + before do + @date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + end + + it "returns non-empty array if there are available rooms" do + num_rooms = 2 + block = @admin.make_block(@date_range, num_rooms) + @admin.check_block(block).must_equal [1,2] + end + + it "returns empty array if there are no rooms available" do + num_rooms = 2 + block = @admin.make_block(@date_range, num_rooms) + rooms = block[:rooms] + rooms.each do |room| + id = room.room_id + @admin.add_reservation_in_block(id) + end + @admin.check_block(block).must_equal [] + end + end + + describe "#get_block" do + before do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + num_rooms = 3 + @block = @admin.make_block(date_range, num_rooms) + end + + it "returns a block for rooms in block" do + @admin.get_block(1).must_equal @block + @admin.get_block(2).must_equal @block + @admin.get_block(3).must_equal @block + end + + it "returns empty {} if id is not in block" do + @admin.get_block(4).wont_equal @block + end + end + +end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..78b37c549 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,67 @@ +require_relative 'spec_helper' + +describe "Reservation Class" do + before do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + + room = Hotel::Room.new(1, false) + @reservation = Hotel::Reservation.new(date_range, room) + end + + describe "Initializer" do + it "is an instance of Reservation" do + @reservation.must_be_instance_of Hotel::Reservation + end + + it "correctly initializes instance variables" do + @reservation.start_date.must_be_kind_of Date + @reservation.end_date.must_be_kind_of Date + @reservation.cost_per_night.must_be_kind_of Integer + @reservation.total_cost.must_be_kind_of Integer + @reservation.room.must_be_instance_of Hotel::Room + end + end + + describe "#calculate_cost" do + it "accurately calculates cost" do + @reservation.total_cost.must_equal 800 + end + + it "raises StandardError if start_date == end_date" do + zero_days = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 05) + } + room = Hotel::Room.new(1, true) + + proc { + Hotel::Reservation.new(zero_days, room) + }.must_raise StandardError + end + + it "accurately calculates cost for one day" do + one_day = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 06) + } + room = Hotel::Room.new(1, true) + reservation = Hotel::Reservation.new(one_day, room) + reservation.total_cost.must_equal 200 + end + + it "accurately calculates cost for room in a block with 4 rooms" do + date_range = { + start_date: Date.new(2018, 03, 05), + end_date: Date.new(2018, 03, 10) + } + room = Hotel::Room.new(1, true, is_in_block: true) + cost = 160 #cost_per_night for 4 rooms + reservation = Hotel::Reservation.new(date_range, room, cost_per_night: cost) + reservation.total_cost.must_equal 640 + end + end + +end diff --git a/specs/room_spec.rb b/specs/room_spec.rb new file mode 100644 index 000000000..c161fd278 --- /dev/null +++ b/specs/room_spec.rb @@ -0,0 +1,22 @@ +require_relative 'spec_helper' + +describe "Room Class" do + before do + id = 1 + is_reserved = false + @room = Hotel::Room.new(id, is_reserved, is_in_block: false) + # @room = Hotel::Room.new(id, is_reserved) + end + + describe "Initializer" do + it "is an instance of Room" do + @room.must_be_instance_of Hotel::Room + end + + it "creates initial data structures" do + @room.is_reserved.must_equal false + @room.room_id.must_be_kind_of Integer + @room.is_in_block.must_equal false + end + end +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..867f5c5b0 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,12 @@ +require 'simplecov' +SimpleCov.start + +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new + +require_relative '../lib/admin' +require_relative '../lib/reservation' +require_relative '../lib/room'