diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..08c645514 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,77 @@ +What classes does each implementation include? Are the lists the same? + +The three classes are CartEntry, ShoppingCart, and Order. Both implementations hold the same classes. + + + + +Write down a sentence to describe each class. + +CartEntry holds information about an item that has been added to a ShoppingCart. ShoppingCarts holds an array of CartEntries. Order contains a ShoppingCart and calculates the final price of the order. + + + + +How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + +All three classes have a compositional relationship to each other. ShoppingCarts holds an array of CartEntries. Order holds a ShoppingCart. + + + + +What data does each class store? How (if at all) does this differ between the two implementations? + +The data stored in each class of both implementations are the same. CartEntry stores information on net price and quantity. ShoppingCart stores information on entries. And Order stores a ShoppingCart and the sales tax. + + + + +What methods does each class have? How (if at all) does this differ between the two implementations? + +In Implementation A, CartEntry and ShoppingCart do not contain any methods. Order contains the method total price. + +In Implementation B, CartEntry contains the price method, ShoppingCart contains a different price method, and Order contains the total price method. + + + + +Consider the Order#total_price method. In each implementation: + +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 all within the Order class. In Implementation B, the logic is delegated between all classes. + + + + +Does total_price directly manipulate the instance variables of other classes? +total_price reads the instance variables of other classes but it doesn't write to them. + + + + +If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? +I would need more information on the logic behind bulk pricing. If it's the same discount for all products once they hit a certain quantity limit then it could go into wherever the price calculations methods are as a percent multiplier if quantity > bulk_quantity_min. Or if the discount differs from product to product, it could be stored as one or more instance variables under CartEntry (e.g. bulk_price, bulk_quantity_min, or discount). + +Implementation B would be easier to modify. + + + + + +Which implementation better adheres to the single responsibility principle? + +Implementation B. + + + + + + +Revisiting Hotel +In my previous code, System calls on some of HotelBlock's instance variables to make a new reservation. Instead, I will now change the code so that HotelBlock makes the reservation and return the reservation to be stored in HotelBlock. + + + + + diff --git a/lib/custom_errors.rb b/lib/custom_errors.rb new file mode 100644 index 000000000..7a3275333 --- /dev/null +++ b/lib/custom_errors.rb @@ -0,0 +1,2 @@ +class FullOccupancyError < StandardError +end diff --git a/lib/hotelblock.rb b/lib/hotelblock.rb new file mode 100644 index 000000000..e08ee44ac --- /dev/null +++ b/lib/hotelblock.rb @@ -0,0 +1,26 @@ +require 'date' +require_relative 'reservation' + +module Hotel + class HotelBlock < Reservation + + attr_reader :status + + # id here is the id of the hotel block + # shared by all rooms within same block + def initialize(id:, room:, start_date:, end_date:, cost:) + super + @status = :AVAILABLE + end + + def change_status + @status = :UNAVAILABLE + end + + def make_res_from_hb(reservation_id) + new_reservation = Hotel::Reservation.new(id: reservation_id, room: @room, start_date: @start_date, end_date: @end_date, cost: @cost) + return new_reservation + end + end +end + diff --git a/lib/hoteldate.rb b/lib/hoteldate.rb new file mode 100644 index 000000000..313c67533 --- /dev/null +++ b/lib/hoteldate.rb @@ -0,0 +1,34 @@ +require_relative 'room' +require_relative 'reservation' + +module Hotel + class Date + + attr_reader :id, :occupied + + def initialize(id) + # id is a date object + @id = id + # occupied is a hash with key room_id and values of reservation instance + @occupied = {} + end + + # Adds either reservation or hotel blocks to @occupied hash. + # Below the parameter is called "reservation" but + # logic also works for hotel blocks. + def add_occupancy(reservation) + @occupied[reservation.room] = reservation + end + + # Returns only reservations and not hotel blocks as per specs. + def list_reservations + return @occupied.values.select { |value| value.class == Hotel::Reservation } + end + + # Returns all occupied rooms, either reserved or blocked. + def rooms_unavailable + return @occupied.keys + end + + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..a5fe3d789 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,26 @@ +require 'date' +require_relative 'room' + +module Hotel + class Reservation + + attr_reader :id, :room, :start_date, :end_date, :cost + + def initialize(id:, room:, start_date:, end_date:, cost: room.cost) + if end_date <= start_date + raise ArgumentError, 'End date must be after start date.' + end + + @id = id + @room = room # this is a room object + @start_date = start_date # this is a date instance + @end_date = end_date # this is a date instance + @cost = cost + end + + def find_total_cost + return (@end_date - @start_date) * @cost.to_f + end + + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..d2827477b --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,22 @@ +module Hotel + class Room + + attr_reader :id, :cost + + def initialize(id, cost) + @id = id + @cost = cost + end + + def self.generate_rooms + array_of_room_obj = [] + + (1..20).each do |i| + array_of_room_obj << Hotel::Room.new(i, 200.0) + end + + return array_of_room_obj + end + + end +end diff --git a/lib/system.rb b/lib/system.rb new file mode 100644 index 000000000..9276ee357 --- /dev/null +++ b/lib/system.rb @@ -0,0 +1,151 @@ +require 'date' + +require_relative 'room' +require_relative 'reservation' +require_relative 'hoteldate' +require_relative 'hotelblock' +require_relative 'custom_errors' + +module Hotel + class System + + attr_reader :rooms, :reservations, :dates, :hotelblocks + + def initialize + @rooms = Hotel::Room.generate_rooms + @reservations = [] + + # Hotel::Date objects hold information on reservations and hotel blocks + # made on a specific date + @dates = [] + + # Keys will be hotel block ids (shared by all rooms within block). + # Values will be arrays of hotel block objects within same block. + # Each array element represents one room. + @hotelblocks = {} + end + + def find_room(room_id) + return @rooms.find { |room| room.id == room_id } + end + + def find_date(date_obj) + return @dates.find { |hotel_date| hotel_date.id == date_obj } + end + + def find_all_available_rooms(start_date, end_date) + current_date = start_date.dup + available_rooms = @rooms.dup + + until current_date == end_date + hotel_date = find_date(current_date) + available_rooms -= hotel_date.rooms_unavailable if hotel_date + current_date += 1 + end + + return available_rooms + end + + def make_reservation(start_date, end_date) + id = @reservations.length + 1 + + # assign the first available room to the reservation + room = find_all_available_rooms(start_date, end_date)[0] + raise FullOccupancyError.new('No rooms available for the date range.') if room == nil + + new_reservation = Hotel::Reservation.new(id: id, room: room, start_date: start_date, end_date: end_date) + + @reservations << new_reservation + add_to_dates(start_date, end_date, new_reservation) + + return new_reservation + end + + # new_occupancy refers to reservation or hotel block to be added to dates + def add_to_dates(start_date, end_date, new_occupancy) + current_date = start_date.dup + + until current_date == end_date + date_obj = find_date(current_date) + + if date_obj + date_obj.add_occupancy(new_occupancy) + else + new_date_obj = Hotel::Date.new(current_date) + @dates << new_date_obj + new_date_obj.add_occupancy(new_occupancy) + end + + current_date += 1 + end + end + + def list_reservations_for(date) + hotel_date = find_date(date) + + if hotel_date + return hotel_date.list_reservations + else + return nil + end + end + + def create_hotelblock(start_date:, end_date:, hb_rooms:, discount_rate:) + # hb_rooms is an array of room_ids + hb_rooms.map! { |room_id| find_room(room_id) } + + if hb_rooms.length > 5 + raise ArgumentError, 'A block can only contain a maximum of 5 rooms.' + end + + available_rooms = find_all_available_rooms(start_date, end_date) + + # Change hb_rooms into a array of boolean values for whether they are available + # Does the array contain false (room is not available)? + # If so raise an error. + if hb_rooms.map{ |room| available_rooms.include? room}.include? false + raise ArgumentError, 'Block contains room that is already booked.' + end + + hb_id = @hotelblocks.length + 1 + @hotelblocks[hb_id] = [] + + hb_rooms.each do |room| + new_hotel_block = Hotel::HotelBlock.new(id: hb_id, room: room, start_date: start_date, end_date: end_date, cost:discount_rate.to_f) + + @hotelblocks[hb_id] << new_hotel_block + + add_to_dates(start_date, end_date, new_hotel_block) + end + end + + def find_open_rooms_from_block(hb_id) + hotel_blocks = @hotelblocks[hb_id] + open_rooms = hotel_blocks.select { |hotel_block| hotel_block.status == :AVAILABLE} + + # Return open rooms as room IDs rather than Room objects for end user readability. + open_rooms.map! { |hotel_block| hotel_block.room.id} + + return open_rooms + end + + def reserve_from_block(hb_id, room_id) + unless find_open_rooms_from_block(hb_id).include? room_id + raise ArgumentError, 'The room you are trying to book is not available.' + end + + # Change room status so room can no longer be booked. + hotel_block = @hotelblocks[hb_id].find {|hb| hb.room.id == room_id} + hotel_block.change_status + + # Reservation is hardcoded to use hotel block dates and cost. + reservation_id = @reservations.length + 1 + + new_reservation = hotel_block.make_res_from_hb(reservation_id) + + @reservations << new_reservation + end + + end +end + diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..97ec80681 --- /dev/null +++ b/refactors.txt @@ -0,0 +1,19 @@ +Possible future changes: +- limit reservations to 30 days at a time maximum to discourage users from making long reservations + - include validation for date ranges to throw error if date range is over 30 days + +- change Hotel::HotelBlock to simply Hotel::Block + - cmd + shift + F for HotelBlock and hotel_block across all files + - change delete hotel or hotel_ (case insensitive) + +Possible but improbable future changes: +- remove room and date class + - requires rewriting the entire code + - reduces dependencies + - however, will shift most of the responsibilities to system class + - difficult to flesh out concrete ideas + - room class was created to allow for easier refactoring if room pricing structure changed + - date class allows for more readable code by creating another database that acts as a calendar + - tracking room availability can be done by iterating through all reservations & hotel blocks to look for date matches + - if match, mark the room as unavailable + - or if match, add the reservation to an array to be returned after iteration complete diff --git a/test/hotelblock_test.rb b/test/hotelblock_test.rb new file mode 100644 index 000000000..0142897c3 --- /dev/null +++ b/test/hotelblock_test.rb @@ -0,0 +1,73 @@ +require_relative 'test_helper' + +describe "HotelBlock class" do + describe "HotelBlock instantiation" do + before do + room_15 = Hotel::Room.new(15, 200.00) + start_time = ::Date.parse('2019-09-03') + end_time = ::Date.parse('2019-09-08') + + @hotelblock = Hotel::HotelBlock.new(id: 101, room: room_15, start_date: start_time, end_date: end_time, cost: 100) + end + + it "is an instance of HotelBlock" do + expect(@hotelblock).must_be_kind_of Hotel::HotelBlock + end + + it "is set up for specific attributes and data types" do + [:id, :room, :start_date, :end_date, :cost, :status].each do |prop| + expect(@hotelblock).must_respond_to prop + end + + expect(@hotelblock.id).must_be_instance_of Integer + expect(@hotelblock.room).must_be_instance_of Hotel::Room + expect(@hotelblock.start_date).must_be_instance_of Date + expect(@hotelblock.end_date).must_be_instance_of Date + expect(@hotelblock.status).must_be_instance_of Symbol + end + + it "correctly assigns cost value" do + expect(@hotelblock.cost).must_be_close_to 100.0 + end + + it "correctly calculates total_cost" do + expect(@hotelblock.find_total_cost).must_be_close_to 500.0 + end + end + + describe "the status of hotelblock" do + before do + room_15 = Hotel::Room.new(15, 200.00) + start_time = ::Date.parse('2019-09-03') + end_time = ::Date.parse('2019-09-08') + + @hotelblock = Hotel::HotelBlock.new(id: 101, room: room_15, start_date: start_time, end_date: end_time, cost: 100) + end + + it "initializes hotelblocks with :AVAILABLE" do + expect(@hotelblock.status).must_equal :AVAILABLE + end + + it "can be changed to :UNVAILABLE with the change_status method" do + @hotelblock.change_status + expect(@hotelblock.status).must_equal :UNAVAILABLE + end + end + + describe "raises an exception when an invalid date range is provided" do + before do + @room_15 = Hotel::Room.new(15, 200.00) + @start_time = ::Date.parse('2019-09-03') + end + + it "raises an error if end date is before start date" do + end_time = ::Date.parse('2019-09-01') + + expect{Hotel::HotelBlock.new(id: 101, room: @room_15, start_date: @start_time, end_date: end_time, cost: 100)}.must_raise ArgumentError + end + + it "raises an error if end date is on same day as start date" do + expect{Hotel::HotelBlock.new(id: 101, room: @room_15, start_date: @start_time, end_date: @start_time, cost: 100)}.must_raise ArgumentError + end + end +end diff --git a/test/hoteldate_test.rb b/test/hoteldate_test.rb new file mode 100644 index 000000000..e16639b89 --- /dev/null +++ b/test/hoteldate_test.rb @@ -0,0 +1,69 @@ +require_relative 'test_helper' + +describe "Hotel::Date class" do + describe "Hotel::Date instantiation" do + before do + @hotel_date = Hotel::Date.new(::Date.parse('2019-09-03')) + end + + it "is an instance of Hotel::Date" do + expect(@hotel_date).must_be_kind_of Hotel::Date + end + + it "is set up for specific attributes and data types" do + [:id, :occupied].each do |prop| + expect(@hotel_date).must_respond_to prop + end + + expect(@hotel_date.id).must_be_instance_of Date + expect(@hotel_date.occupied).must_be_instance_of Hash + end + + end + + describe "Hotel::Date instance methods" do + before do + @hotel_date = Hotel::Date.new(::Date.parse('2019-09-03')) + + @room_15 = Hotel::Room.new(15, 200.00) + start_time = ::Date.parse('2019-09-03') + end_time = ::Date.parse('2019-09-08') + @reservation = Hotel::Reservation.new(id: 101, room: @room_15, start_date: start_time, end_date: end_time) + + @room_9 = Hotel::Room.new(9, 200.00) + start_time = ::Date.parse('2019-09-03') + end_time = ::Date.parse('2019-09-08') + @hotel_block = Hotel::HotelBlock.new(id: 201, room: @room_9, start_date: start_time, end_date: end_time, cost:145) + + @hotel_date.add_occupancy(@reservation) + @hotel_date.add_occupancy(@hotel_block) + end + + it "can add a reservation" do + expect(@hotel_date.occupied.keys.first).must_be_instance_of Hotel::Room + expect(@hotel_date.occupied.values.first).must_be_instance_of Hotel::Reservation + expect(@hotel_date.occupied[@room_15]).must_equal @reservation + end + + it "can add a hotel block" do + expect(@hotel_date.occupied.keys.last).must_be_instance_of Hotel::Room + expect(@hotel_date.occupied.values.last).must_be_instance_of Hotel::HotelBlock + expect(@hotel_date.occupied[@room_9]).must_equal @hotel_block + end + + it "returns an array of reservations for given date, excludes hotel blocks" do + expect(@hotel_date.list_reservations).must_be_instance_of Array + + expect(@hotel_date.list_reservations.length).must_equal 1 + expect(@hotel_date.list_reservations[0]).must_equal @reservation + end + + it "returns an array of occupied rooms under a given date" do + expect(@hotel_date.rooms_unavailable).must_be_instance_of Array + + expect(@hotel_date.rooms_unavailable.length).must_equal 2 + expect(@hotel_date.rooms_unavailable[0]).must_equal @room_15 + expect(@hotel_date.rooms_unavailable[1]).must_equal @room_9 + end + end +end diff --git a/test/reservation_test.rb b/test/reservation_test.rb new file mode 100644 index 000000000..2ee407812 --- /dev/null +++ b/test/reservation_test.rb @@ -0,0 +1,54 @@ +require_relative 'test_helper' + +describe "Reservation class" do + describe "Reservation instantiation" do + before do + room_15 = Hotel::Room.new(15, 200.00) + start_time = ::Date.parse('2019-09-03') + end_time = ::Date.parse('2019-09-08') + + @reservation = Hotel::Reservation.new(id: 101, room: room_15, start_date: start_time, end_date: end_time) + end + + it "is an instance of Reservation" do + expect(@reservation).must_be_kind_of Hotel::Reservation + end + + it "is set up for specific attributes and data types" do + [:id, :room, :start_date, :end_date, :cost].each do |prop| + expect(@reservation).must_respond_to prop + end + + expect(@reservation.id).must_be_instance_of Integer + expect(@reservation.room).must_be_instance_of Hotel::Room + expect(@reservation.start_date).must_be_instance_of Date + expect(@reservation.end_date).must_be_instance_of Date + expect(@reservation.cost).must_be_instance_of Float + end + + it "correctly assigns cost value" do + expect(@reservation.cost).must_be_close_to 200.0 + end + + it "correctly calculates total_cost" do + expect(@reservation.find_total_cost).must_be_close_to 200.0*5 + end + end + + describe "raises an exception when an invalid date range is provided" do + before do + @room_15 = Hotel::Room.new(15, 200.00) + @start_time = ::Date.parse('2019-09-03') + end + + it "raises an error if end date is before start date" do + end_time = ::Date.parse('2019-09-01') + + expect{Hotel::Reservation.new(id: 101, room: @room_15, start_date: @start_time, end_date: end_time)}.must_raise ArgumentError + end + + it "raises an error if end date is on same day as start date" do + expect{Hotel::Reservation.new(id: 101, room: @room_15, start_date: @start_time, end_date: @start_time)}.must_raise ArgumentError + end + end +end diff --git a/test/room_test.rb b/test/room_test.rb new file mode 100644 index 000000000..c2d51250a --- /dev/null +++ b/test/room_test.rb @@ -0,0 +1,38 @@ +require_relative 'test_helper' + +describe "Room class" do + describe "Room instantiation" do + before do + @room = Hotel::Room.new(15, 200.00) + end + + it "is an instance of Room" do + expect(@room).must_be_kind_of Hotel::Room + end + + it "is set up for specific attributes and data types" do + [:id, :cost].each do |prop| + expect(@room).must_respond_to prop + end + + expect(@room.id).must_be_instance_of Integer + expect(@room.cost).must_be_instance_of Float + end + + describe "Room generation" do + before do + @room_array = Hotel::Room.generate_rooms + end + + it "generates an array of rooms" do + expect(@room_array).must_be_instance_of Array + expect(@room_array.first).must_be_kind_of Hotel::Room + expect(@room_array.last).must_be_kind_of Hotel::Room + end + + it "generates the correct number of rooms" do + expect(@room_array.length).must_equal 20 + end + end + end +end diff --git a/test/system_test.rb b/test/system_test.rb new file mode 100644 index 000000000..573b82476 --- /dev/null +++ b/test/system_test.rb @@ -0,0 +1,290 @@ +require_relative 'test_helper' + +describe "System class" do + describe "System instantiation" do + before do + @sys = Hotel::System.new + end + + it "is an instance of System" do + expect(@sys).must_be_instance_of Hotel::System + end + + it "is set up for specific attributes and data types" do + [:rooms, :reservations, :dates, :hotelblocks].each do |prop| + expect(@sys).must_respond_to prop + end + + expect(@sys.rooms).must_be_instance_of Array + expect(@sys.rooms.first).must_be_instance_of Hotel::Room + expect(@sys.rooms.last).must_be_instance_of Hotel::Room + expect(@sys.reservations).must_be_instance_of Array + expect(@sys.dates).must_be_instance_of Array + expect(@sys.hotelblocks).must_be_instance_of Hash + end + + it "allows access to the list of all rooms" do + expect(@sys.rooms.length).must_equal 20 + expect(@sys.rooms[11].id).must_equal 12 + expect(@sys.rooms[11].cost).must_equal 200.0 + end + end + + describe "room reservation creation" do + before do + @sys = Hotel::System.new + @first_res = @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) + end + + it "generates reserves a room for a given date range" do + expect(@first_res).must_be_instance_of Hotel::Reservation + + expect(@first_res.id).must_equal 1 + expect(@first_res.start_date).must_equal ::Date.parse('2019-06-05') + expect(@first_res.end_date).must_equal ::Date.parse('2019-06-08') + expect(@first_res.cost).must_equal 200.0 + expect(@first_res.find_total_cost).must_equal (3*200.0) + end + + it "adds the new reservation to the list of reservations" do + expect(@sys.reservations.length).must_equal 1 + expect(@sys.reservations.first.id).must_equal @first_res.id + end + + it "adds the date range of the reservation for which nights are occupied to the list of dates" do + expect(@sys.dates.length).must_equal 3 + expect(@sys.dates[0].id).must_equal ::Date.parse('2019-06-05') + expect(@sys.dates[1].id).must_equal ::Date.parse('2019-06-06') + expect(@sys.dates[2].id).must_equal ::Date.parse('2019-06-07') + end + + it "does not create a new Hotel::Date object when one already exists" do + second_res = @sys.make_reservation(::Date.parse('2019-06-06'), ::Date.parse('2019-06-09')) + + expect(@sys.reservations.length).must_equal 2 + expect(@sys.dates.length).must_equal 4 + + expect(@sys.dates[0].id).must_equal ::Date.parse('2019-06-05') + expect(@sys.dates[1].id).must_equal ::Date.parse('2019-06-06') + expect(@sys.dates[2].id).must_equal ::Date.parse('2019-06-07') + expect(@sys.dates[3].id).must_equal ::Date.parse('2019-06-08') + + expect(@sys.dates[0].occupied.length).must_equal 1 + expect(@sys.dates[1].occupied.length).must_equal 2 + expect(@sys.dates[2].occupied.length).must_equal 2 + expect(@sys.dates[3].occupied.length).must_equal 1 + end + + it "creates reservations using available rooms" do + expect(@first_res.room.id).must_equal 1 + + second_res = @sys.make_reservation(::Date.parse('2019-06-01'), ::Date.parse('2019-06-06')) + expect(second_res.room.id).must_equal 2 + + third_res = @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-10')) + expect(third_res.room.id).must_equal 3 + + fourth_res = @sys.make_reservation(::Date.parse('2019-06-07'), ::Date.parse('2019-06-10')) + expect(fourth_res.room.id).must_equal 2 + end + + it "returns an error when no rooms are available" do + 19.times do + @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) + end + + expect{ @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) }.must_raise FullOccupancyError + end + end + + describe "reservation overlap verification" do + before do + @sys = Hotel::System.new + @first_res = @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) + end + + it "reservations do not impact list of available rooms outside of date range" do + same_date_range = @sys.find_all_available_rooms(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) + well_before_start_date = @sys.find_all_available_rooms(::Date.parse('2019-01-11'), ::Date.parse('2019-01-22')) + well_after_end_date = @sys.find_all_available_rooms(::Date.parse('2019-10-11'), ::Date.parse('2019-10-22')) + + expect(same_date_range.length).must_equal 19 + expect(well_before_start_date.length).must_equal 20 + expect(well_after_end_date.length).must_equal 20 + end + + it "allows reservation of room in which check-in day is the same as another reservation's check-out day" do + start_date_is_end_date = @sys.find_all_available_rooms(::Date.parse('2019-06-08'), ::Date.parse('2019-06-10')) + + expect(start_date_is_end_date.length).must_equal 20 + expect(@first_res.room.id).must_equal 1 + expect(start_date_is_end_date.include? 1).must_equal false + end + + it "excludes rooms for reservations if they have smaller pre-existing reservations within date range" do + @second_res = @sys.make_reservation(::Date.parse('2019-06-06'), ::Date.parse('2019-06-10')) + @third_res = @sys.make_reservation(::Date.parse('2019-06-07'), ::Date.parse('2019-06-09')) + + spans_all_res = @sys.find_all_available_rooms(::Date.parse('2019-06-01'), ::Date.parse('2019-06-12')) + expect(spans_all_res.length).must_equal 17 + end + end + + describe "list reservations for date method" do + before do + @sys = Hotel::System.new + end + + it "returns nil when no reservations have been made for given date" do + res_list = @sys.list_reservations_for(::Date.parse('2020-08-09')) + + expect(res_list).must_be_nil + end + + it "returns an array of reservations for a given date" do + first_res = @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) + second_res = @sys.make_reservation(::Date.parse('2019-06-06'), ::Date.parse('2019-06-09')) + + res_list = @sys.list_reservations_for(::Date.parse('2019-06-06')) + expect(res_list.length).must_equal 2 + expect(res_list.include? first_res).must_equal true + expect(res_list.include? second_res).must_equal true + end + end + + describe "Finds Available Rooms" do + before do + @sys = Hotel::System.new + end + + it "can find the correct room given a room_id" do + room_obj = @sys.find_room(16) + + expect(room_obj).must_be_instance_of Hotel::Room + expect(room_obj.id).must_equal 16 + end + + it "returns list of rooms that are not reserved for a given date range" do + 10.times do + @sys.make_reservation(::Date.parse('2019-06-05'), ::Date.parse('2019-06-08')) + end + + start_date = ::Date.parse('2019-06-05') + end_date = ::Date.parse('2019-06-08') + + available_rooms = @sys.find_all_available_rooms(start_date, end_date) + + expect(available_rooms).must_be_instance_of Array + expect(available_rooms.length).must_equal 10 + expect(available_rooms.first.id).must_equal 11 + expect(available_rooms.last.id).must_equal 20 + end + end + + describe "HotelBlock Creation" do + before do + @sys = Hotel::System.new + start_date = ::Date.parse('2019-09-02') + end_date = ::Date.parse('2019-09-05') + @sys.create_hotelblock(start_date: start_date, end_date: end_date, hb_rooms: [1,2,3,4], discount_rate: 165) + end + + it "adds to the hotelblocks list" do + expect(@sys.hotelblocks.length).must_equal 1 + expect(@sys.hotelblocks.keys.first).must_equal 1 + expect(@sys.hotelblocks.values.first).must_be_instance_of Array + expect(@sys.hotelblocks[1].first).must_be_instance_of Hotel::HotelBlock + expect(@sys.hotelblocks[1][2].room.id).must_equal 3 + end + + it "updates the hotel.dates list" do + expect(@sys.dates.length).must_equal 3 + + expect(@sys.dates.first).must_be_instance_of Hotel::Date + + expect(@sys.dates[0].id).must_equal ::Date.parse('2019-09-02') + expect(@sys.dates[1].id).must_equal ::Date.parse('2019-09-03') + expect(@sys.dates[2].id).must_equal ::Date.parse('2019-09-04') + end + + it "excludes the room from reservation during the same date range" do + new_reservation = @sys.make_reservation(::Date.parse('2019-09-04'), ::Date.parse('2019-09-07')) + expect(new_reservation.room.id).must_equal 5 + + 15.times do + @sys.make_reservation(::Date.parse('2019-09-04'), ::Date.parse('2019-09-07')) + end + + expect{@sys.make_reservation(::Date.parse('2019-09-04'), ::Date.parse('2019-09-07'))}.must_raise FullOccupancyError + end + + it "excludes the room from be added to hotel block during the same date range" do + start_date = ::Date.parse('2019-09-04') + end_date = ::Date.parse('2019-09-07') + + expect{@sys.create_hotelblock(start_date: start_date, end_date: end_date, hb_rooms: [2,5,6,7], discount_rate: 165)}.must_raise ArgumentError + end + + it "raises an error when trying to create a block of more than 5 rooms" do + start_date = ::Date.parse('2019-09-02') + end_date = ::Date.parse('2019-09-05') + + expect{@sys.create_hotelblock(start_date: start_date, end_date: end_date, hb_rooms: [1,2,3,4,5,6], discount_rate: 165)}.must_raise ArgumentError + end + + it "allows blocking of room in which check-in day is the same as another block's check-out day" do + @sys.create_hotelblock(start_date: ::Date.parse('2019-09-05'), end_date: ::Date.parse('2019-09-09'), hb_rooms: [1,2,3,4], discount_rate: 140) + + expect(@sys.hotelblocks[2].first.start_date).must_equal ::Date.parse('2019-09-05') + end + end + + describe "Reserving from a HotelBlock" do + before do + @sys = Hotel::System.new + start_date = ::Date.parse('2019-09-02') + end_date = ::Date.parse('2019-09-05') + hotel_block = @sys.create_hotelblock(start_date: start_date, end_date: end_date, hb_rooms: [7,8,9,10], discount_rate: 165) + + @sys.reserve_from_block(1, 8) + end + + it "can make a reservation from a hotel block" do + expect(@sys.find_open_rooms_from_block(1)).must_equal [7,9,10] + expect(@sys.reservations[0].room.id).must_equal 8 + end + + it "makes reservations for the duration of the hotel block" do + expect(@sys.reservations[0].start_date).must_equal ::Date.parse('2019-09-02') + expect(@sys.reservations[0].end_date).must_equal ::Date.parse('2019-09-05') + end + + it "returns all available rooms in a HotelBlock" do + available_rooms = @sys.find_open_rooms_from_block(1) + + expect(available_rooms.length).must_equal 3 + expect(available_rooms).must_equal [7,9,10] + end + + it "raises an error when reserving a room that's already reserved" do + expect{ @sys.reserve_from_block(1, 8) }.must_raise ArgumentError + end + + it "adds the reservation made from the HotelBlock into the Reservations list" do + @sys.reserve_from_block(1, 10) + + expect(@sys.reservations[1].room.id).must_equal 10 + expect(@sys.reservations[1]).must_be_instance_of Hotel::Reservation + end + + it "returns nil if no rooms are available" do + @sys.reserve_from_block(1, 10) + @sys.reserve_from_block(1, 7) + @sys.reserve_from_block(1, 9) + + available_rooms = @sys.find_open_rooms_from_block(1) + + expect(available_rooms).must_equal [] + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..65ca40400 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,10 @@ -# Add simplecov +require 'simplecov' +SimpleCov.start do + add_filter 'test/' # Tests should not be counted toward coverage. +end + +require 'date' + require "minitest" require "minitest/autorun" require "minitest/reporters" @@ -6,3 +12,9 @@ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # require_relative your lib files here! +require_relative '../lib/hoteldate' +require_relative '../lib/hotelblock' +require_relative '../lib/reservation' +require_relative '../lib/room' +require_relative '../lib/system' +require_relative '../lib/custom_errors'