diff --git a/.gitignore b/.gitignore index 5e1422c9c..8d2808a00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store *.gem *.rbc /.config diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..b9830520b --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,17 @@ +module Hotel + class Block + attr_reader :id, :rooms, :rate, :date_range + + def initialize(id:, rooms:, rate:, start_date:, end_date:) + @id = id + @rooms = rooms + @rate = rate + @date_range = Hotel::DateRange.new( + start_date: start_date, + end_date: end_date + ) + + raise ArgumentError.new("Block must contain between 2 and 5 rooms!") if @rooms.length < 2 || @rooms.length > 5 + end + end +end \ No newline at end of file diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..75752cf01 --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,38 @@ +module Hotel + class DateRange + attr_accessor :start_date, :end_date + + def initialize(start_date:, end_date:) + @start_date = start_date + @end_date = end_date + + raise ArgumentError.new("Invalid date range!") if @start_date > @end_date + end + + def nights + return (@end_date - @start_date).to_i + end + + def overlap?(other) + return @start_date <= other.end_date && @end_date >= other.start_date + end + + def include?(date) + low = 0 + high = self.nights + + while low <= high + mid = (low + high) / 2 + if (@start_date + mid) == date + return true + elsif (@start_date + mid) > date + high = mid - 1 + elsif (@start_date + mid) < date + low = mid + 1 + end + end + + return false + end + end +end \ No newline at end of file diff --git a/lib/front_desk.rb b/lib/front_desk.rb new file mode 100644 index 000000000..dffdd1368 --- /dev/null +++ b/lib/front_desk.rb @@ -0,0 +1,103 @@ +module Hotel + class FrontDesk + attr_reader :rooms, :reservations, :blocks + + def initialize + @rooms = (1..20).to_a + @reservations = [] + @blocks = [] + end + + def add_reservation(reservation) + @reservations << reservation + end + + def reservations_by_room(room, date_range) + return @reservations.select { |reservation| + reservation.room == room && reservation.date_range.overlap?(date_range) + } + end + + def reservations_by_date(date) + return @reservations.select { |reservation| + reservation.date_range.include?(date) + } + end + + def find_available_room(date_range) + unavailable_rooms = @reservations.map { |reservation| + reservation.room if reservation.date_range.start_date < date_range.end_date && reservation.date_range.end_date > date_range.start_date + } + @blocks.each { |block| + (block.rooms).each { |room| unavailable_rooms << room } if block.date_range.start_date < date_range.end_date && block.date_range.end_date > date_range.start_date + } # This method is very expensive. See refactors.txt for details. + available_rooms = @rooms - unavailable_rooms + + raise ArgumentError.new("No rooms available for that date range!") if available_rooms.empty? + return available_rooms + end + + def reserve_room(date_range, rate = 200.0) + new_reservation = Hotel::Reservation.new( + id: @reservations.length + 1, + room: find_available_room(date_range).first, + rate: rate.to_f, + start_date: date_range.start_date, + end_date: date_range.end_date + ) + add_reservation(new_reservation) + return new_reservation + end + + def add_block(block) + @blocks << block + end + + def find_block(id) + block = @blocks.find { |block| block.id == id } + raise ArgumentError.new("No blocks with the given ID!") if block.nil? + return block + end + + def reserve_block(rooms, rate, date_range) + rooms.each { |room| + raise ArgumentError.new("At least one of the rooms is unavailable for the given date range!") if !find_available_room(date_range).include?(room) + } + + new_block = Hotel::Block.new( + id: @blocks.length + 1, + rooms: rooms, + rate: rate, + start_date: date_range.start_date, + end_date: date_range.end_date + ) + add_block(new_block) + return new_block + end + + def find_available_room_in_block(id) + block = find_block(id) + unavailable_rooms = @reservations.map { |reservation| + reservation.room if reservation.block == block.id + } + available_rooms = block.rooms - unavailable_rooms + + raise ArgumentError.new("No rooms available in the given block!") if available_rooms.empty? + return available_rooms + end + + def reserve_room_in_block(id, room) + block = find_block(id) + new_reservation = Hotel::Reservation.new( + id: @reservations.length + 1, + room: room, + block: block.id, + rate: block.rate, + start_date: block.date_range.start_date, + end_date: block.date_range.end_date + ) + add_reservation(new_reservation) + return new_reservation + end + end +end \ No newline at end of file diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..0c0ee6cdf --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,24 @@ +require_relative 'date_range' + +module Hotel + class Reservation + attr_reader :id, :room, :block, :rate, :date_range + + def initialize(id:, room:, block: false, rate: 200.0, start_date:, end_date:) + @id = id + @room = room + @block = block + @rate = rate.to_f + @date_range = Hotel::DateRange.new( + start_date: start_date, + end_date: end_date + ) + + raise ArgumentError.new("Invalid rate!") if rate.class != Float + end + + def total_cost + return @date_range.nights * @rate + end + end +end \ No newline at end of file diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..c462bb94f --- /dev/null +++ b/refactors.txt @@ -0,0 +1,43 @@ +================= +lib/front_desk.rb +================= + +1) class FrontDesk +- Since the program will be used by employees of the hotel and not the general public, I thought FrontDesk would be more descriptive. But if the program is ported to an online reservation system that is open to the general public, the class name should probably change to HotelController or ReservationSystem. + +2) #find_available_room +- This method is very expensive because it iterates through two arrays of unknown size (time complexity is O(n^2)) and it creates a new array in memory each time it adds a room to the unavailable_rooms list (space complexity is O(n)). +- If I created a Room class that knew its Reservations and/or the Blocks it belonged to during a DateRange, I could refactor this method to just use the #reject enumerable, rejecting any rooms that met the existing conditionals. + +3) any method that has date_range as a parameter +- I was visualizing this program as the backend of a digital form with separate fields for the start and end dates of range (if the function required that information). Each time the 'submit' button is pressed, the program would collect the values of these fields and use them to instantiate a new DateRange object, then pass that object into the method. This is why the start_date and end_date attributes are both readable and writable, so they can be written from outside the program. However, I'm not sure if this will work as intended, so it's possible that many of my tests are passing superficially! + +4) #reserve_room & #reserve_room_in_block +- I may be able to merge these methods into one, because a Reservation can be instantiated without a block and rate attribute. +- If I create a Room class, I could have the rate stored in each Room instead of having it passed into these methods. +- But if the program is ported to an online reservation system that is open to the general public, these methods should stay separate because the general public is not allowed to reserve rooms in blocks. + +================== +lib/reservation.rb +================== + +1) #rate & #total_cost +- If I create a Room class, I could have the rate stored in each Room instead of having it passed into a new Reservation. +- The #total_cost method would then call the room.rate + +2) #start_date & #end_date +- I was thinking that since these attributes can be written over in the DateRange class, they should probably not be stored anywhere else. But if each Reservation was able to call its start_date and end_date, this would simplify some method calls in the FrontDesk class. Perhaps this is an opportunity for inheritance (i.e., class Reservation < DateRange)? + +================= +lib/date_range.rb +================= + +1) #include? +- An earlier version simply returned (start_date..end_date).include?(date), which creates an instance of the Range class, which already has an include? method. I'm wondering if this was a better design, but I also wanted to avoid dependencies (if the Range class changes in the future, any method that creates an instance of that class might break). Instead, I created a binary search method that is not dependent on an additional class. + +============ +lib/block.rb +============ + +1) class Block +- If I create a Room class, there could be an opportunity for inheritance here (i.e., a Block is a Room), which could help with the FrontDesk#find_available_room (see notes above) \ No newline at end of file diff --git a/test/block_test.rb b/test/block_test.rb new file mode 100644 index 000000000..3ddc37dca --- /dev/null +++ b/test/block_test.rb @@ -0,0 +1,50 @@ +require_relative 'test_helper' + +describe "Block class" do + before do + @block = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + end + + describe "Block instantiation" do + it "is an instance of Block" do + expect(@block).must_be_kind_of Hotel::Block + end + + it "is set up for specific attributes and data types" do + [:id, :rooms, :rate, :date_range].each do |attribute| + expect(@block).must_respond_to attribute + end + + expect(@block.id).must_be_kind_of Integer + expect(@block.rooms).must_be_kind_of Array + expect(@block.rate).must_be_kind_of Float + expect(@block.date_range).must_be_kind_of Hotel::DateRange + end + + it "throws an exception if the list of rooms is greater than 5" do + expect{ Hotel::Block.new( + id: 1, + rooms: (1..6).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) }.must_raise ArgumentError + end + + it "throws an exception if the list of rooms is less than 2" do + expect{ Hotel::Block.new( + id: 1, + rooms: [1], + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) }.must_raise ArgumentError + end + end +end \ No newline at end of file diff --git a/test/date_range_test.rb b/test/date_range_test.rb new file mode 100644 index 000000000..a18124b6f --- /dev/null +++ b/test/date_range_test.rb @@ -0,0 +1,108 @@ +require_relative 'test_helper' + +describe "DateRange class" do + before do + @date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + end + + describe "DateRange instantiation" do + it "is an instance of DateRange" do + expect(@date_range).must_be_kind_of Hotel::DateRange + end + + it "is set up for specific attributes and data types" do + [:start_date, :end_date].each do |attribute| + expect(@date_range).must_respond_to attribute + end + + expect(@date_range.start_date).must_be_kind_of Date + expect(@date_range.end_date).must_be_kind_of Date + end + + it "throws an exception when an invalid date range is provided" do + expect{ Hotel::DateRange.new( + start_date: Date.new(2020,3,5), + end_date: Date.new(2020,3,2) + ) }.must_raise ArgumentError + end + end + + describe "#nights" do + it "returns an Integer" do + expect(@date_range.nights).must_be_kind_of Integer + end + + it "calculates the nights of stay accurately" do + expect(@date_range.nights).must_equal 3 + end + end + + describe "#overlap?" do + before do + @date_range2 = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + end + + it "returns true when start_date and end_date are both within the range" do + expect(@date_range.overlap?(@date_range2)).must_equal true + end + + it "returns false when start_date and end_date are both outside the range" do + @date_range2.start_date += 8 + @date_range2.end_date += 8 + expect(@date_range.overlap?(@date_range2)).must_equal false + end + + it "returns true when start_date is within the range but end_date is outside the range" do + @date_range2.start_date += 1 + @date_range2.end_date += 1 + expect(@date_range.overlap?(@date_range2)).must_equal true + end + + it "returns true when the start_date is outside the range but end_date is within the range" do + @date_range2.start_date -= 1 + @date_range2.end_date -= 1 + expect(@date_range.overlap?(@date_range2)).must_equal true + end + + it "returns true when the start_date is equal to the end_date in the range" do + @date_range2.start_date += 3 + @date_range2.end_date += 3 + expect(@date_range.overlap?(@date_range2)).must_equal true + end + + it "returns true when the end_date is equal to the start_date in the range" do + @date_range2.start_date -= 3 + @date_range2.end_date -= 3 + expect(@date_range.overlap?(@date_range2)).must_equal true + end + end + + describe "#include?" do + it "returns true when the date given is at the start of the range" do + date = Date.new(2020,3,2) + expect(@date_range.include?(date)).must_equal true + end + + it "returns true when the date given is at the end of the range" do + date = Date.new(2020,3,5) + expect(@date_range.include?(date)).must_equal true + end + + it "returns true when the date given is in the middle of the range" do + date = Date.new(2020,3,3) + expect(@date_range.include?(date)).must_equal true + end + + it "returns false when the date given is outside the range" do + date = Date.new(2020,3,10) + expect(@date_range.include?(date)).must_equal false + end + end +end + \ No newline at end of file diff --git a/test/front_desk_test.rb b/test/front_desk_test.rb new file mode 100644 index 000000000..bc89f975c --- /dev/null +++ b/test/front_desk_test.rb @@ -0,0 +1,511 @@ +require_relative 'test_helper' + +describe "FrontDesk class" do + before do + @front_desk = Hotel::FrontDesk.new + end + + describe "FrontDesk instantiation" do + it "is an instance of FrontDesk" do + expect(@front_desk).must_be_kind_of Hotel::FrontDesk + end + + it "can access the list of all the rooms in the hotel" do + rooms = @front_desk.rooms + + expect(rooms).must_be_kind_of Array + expect(rooms.first).must_equal 1 + expect(rooms.last).must_equal 20 + end + + it "can access the list of all reservations" do + reservations = @front_desk.reservations + + expect(reservations).must_be_kind_of Array + end + + it "can access the list of all blocks" do + blocks = @front_desk.blocks + + expect(blocks).must_be_kind_of Array + end + end + + describe "#add_reservation" do + it "adds the reservation passed in to the reservations array" do + reservation = Hotel::Reservation.new( + id: 1, + room: 15, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + before_length = @front_desk.reservations.length + @front_desk.add_reservation(reservation) + after_length = @front_desk.reservations.length + + expect(@front_desk.reservations.last).must_be_kind_of Hotel::Reservation + expect(before_length).must_equal 0 + expect(after_length).must_equal 1 + end + end + + describe "#reservations_by_room" do + before do + @reservation1 = Hotel::Reservation.new( + id: 1, + room: 15, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @reservation2 = Hotel::Reservation.new( + id: 2, + room: 20, + start_date: Date.new(2020,3,3), + end_date: Date.new(2020,3,4) + ) + @reservation3 = Hotel::Reservation.new( + id: 3, + room: 15, + start_date: Date.new(2020,3,5), + end_date: Date.new(2020,3,10) + ) + @reservation4 = Hotel::Reservation.new( + id: 4, + room: 15, + start_date: Date.new(2020,3,1), + end_date: Date.new(2020,3,2) + ) + @front_desk.add_reservation(@reservation1) + @front_desk.add_reservation(@reservation2) + @front_desk.add_reservation(@reservation3) + @front_desk.add_reservation(@reservation4) + + date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @selected_reservations = @front_desk.reservations_by_room(15, date_range) + end + + it "returns an array of Reservations" do + expect(@selected_reservations).must_be_kind_of Array + expect(@selected_reservations.first).must_be_kind_of Hotel::Reservation + end + + it "can access the list of reservations for a specified room and a given date range" do + expect(@front_desk.reservations.length).must_equal 4 # ensure that the method isn't just returning the @reservations array + expect(@selected_reservations.length).must_equal 3 + expect(@selected_reservations.first).must_equal @reservation1 + expect(@selected_reservations.last).must_equal @reservation4 + end + end + + describe "#reservations_by_date" do + before do + @reservation1 = Hotel::Reservation.new( + id: 1, + room: 15, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @reservation2 = Hotel::Reservation.new( + id: 2, + room: 20, + start_date: Date.new(2020,3,1), + end_date: Date.new(2020,3,4) + ) + @reservation3 = Hotel::Reservation.new( + id: 3, + room: 15, + start_date: Date.new(2020,3,5), + end_date: Date.new(2020,3,10) + ) + @front_desk.add_reservation(@reservation1) + @front_desk.add_reservation(@reservation2) + @front_desk.add_reservation(@reservation3) + + @selected_reservations = @front_desk.reservations_by_date(Date.new(2020,3,2)) + end + + it "returns an array of Reservations" do + expect(@selected_reservations).must_be_kind_of Array + expect(@selected_reservations.first).must_be_kind_of Hotel::Reservation + end + + it "can access the list of reservations for a specific date" do + expect(@front_desk.reservations.length).must_equal 3 # ensure that the method isn't just returning the @reservations array + expect(@selected_reservations.length).must_equal 2 + expect(@selected_reservations.first).must_equal @reservation1 + expect(@selected_reservations.last).must_equal @reservation2 + end + + it "can see a reservation made from a hotel block for a specific date" do + block = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_block(block) + reservation = @front_desk.reserve_room_in_block(1, 1) + @selected_reservations = @front_desk.reservations_by_date(Date.new(2020,3,2)) + + expect(@selected_reservations).must_include reservation + end + end + + describe "#find_available_room" do + before do + @reservation1 = Hotel::Reservation.new( + id: 1, + room: 15, + start_date: Date.new(2020,2,27), + end_date: Date.new(2020,3,2) + ) + @reservation2 = Hotel::Reservation.new( + id: 2, + room: 20, + start_date: Date.new(2020,3,1), + end_date: Date.new(2020,3,4) + ) + @reservation3 = Hotel::Reservation.new( + id: 3, + room: 6, + start_date: Date.new(2020,3,5), + end_date: Date.new(2020,3,10) + ) + @reservation4 = Hotel::Reservation.new( + id: 4, + room: 9, + start_date: Date.new(2020,3,4), + end_date: Date.new(2020,3,10) + ) + @front_desk.add_reservation(@reservation1) + @front_desk.add_reservation(@reservation2) + @front_desk.add_reservation(@reservation3) + @front_desk.add_reservation(@reservation4) + + rooms = (1..5).to_a + rate = 150.0 + @date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @block = @front_desk.reserve_block(rooms, rate, @date_range) + + @available_rooms = @front_desk.find_available_room(@date_range) + end + + it "returns an array of valid room numbers" do + expect(@available_rooms).must_be_kind_of Array + expect(@available_rooms.first).must_be_kind_of Integer + expect(@front_desk.rooms).must_include @available_rooms.first + expect(@front_desk.rooms).must_include @available_rooms.last + end + + it "returns a list of rooms that are not reserved for a given date range" do + expect(@available_rooms.length).must_equal 13 + expect(@available_rooms).must_include 15 + expect(@available_rooms).must_include 6 + end + + it "returns all rooms if there are no reservations and no blocks" do + @front_desk.reservations.clear + @front_desk.blocks.clear + available_rooms = @front_desk.find_available_room(@date_range) + + expect(@front_desk.reservations.length).must_equal 0 + expect(@front_desk.blocks.length).must_equal 0 + expect(available_rooms).must_equal @front_desk.rooms + end + + it "throws an exception if there are no rooms available" do + @front_desk.rooms.clear + date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + + expect(@front_desk.rooms.length).must_equal 0 + expect{@front_desk.find_available_room(date_range)}.must_raise ArgumentError + end + end + + describe "#reserve_room" do + before do + date_range1 = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + date_range2 = Hotel::DateRange.new( + start_date: Date.new(2020,3,7), + end_date: Date.new(2020,3,10) + ) + date_range3 = Hotel::DateRange.new( + start_date: Date.new(2020,3,12), + end_date: Date.new(2020,3,15) + ) + @reservation1 = @front_desk.reserve_room(date_range1) + @reservation2 = @front_desk.reserve_room(date_range2) + @reservation3 = @front_desk.reserve_room(date_range3) + end + + it "can reserve a room given a start date and an end date" do + expect(@reservation1).must_be_kind_of Hotel::Reservation + end + + it "can set different rates for different rooms" do + date_range4 = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + reservation4 = @front_desk.reserve_room(date_range4, 300.0) + + expect(reservation4.rate).must_equal 300.0 + end + + it "assigns a unique id number to each Reservation" do + expect(@reservation1.id).must_equal 1 + expect(@reservation2.id).must_equal 2 + expect(@reservation3.id).must_equal 3 + end + + it "assigns a valid room number" do + expect(@front_desk.rooms).must_include @reservation1.room + expect(@front_desk.rooms).must_include @reservation2.room + expect(@front_desk.rooms).must_include @reservation3.room + end + + it "adds the new reservations to the reservations array" do + expect(@front_desk.reservations.length).must_equal 3 + expect(@front_desk.reservations.first).must_equal @reservation1 + expect(@front_desk.reservations.last).must_equal @reservation3 + end + end + + describe "#add_block" do + it "adds the block passed in to the blocks array" do + block = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + before_length = @front_desk.blocks.length + @front_desk.add_block(block) + after_length = @front_desk.blocks.length + + expect(@front_desk.blocks.last).must_be_kind_of Hotel::Block + expect(before_length).must_equal 0 + expect(after_length).must_equal 1 + end + end + + describe "find_block" do + before do + @block1 = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @block2 = Hotel::Block.new( + id: 2, + rooms: (6..10).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_block(@block1) + @front_desk.add_block(@block2) + end + + it "throws an exception if no Blocks match id given" do + expect{@front_desk.find_block(3)}.must_raise ArgumentError + end + + it "returns the correct block" do + expect(@front_desk.find_block(2)).must_equal @block2 + end + end + + describe "#reserve_block" do + before do + rooms = (1..5).to_a + rate = 150.0 + date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @block = @front_desk.reserve_block(rooms, rate, date_range) + end + + it "can reserve a block given a collection of rooms, a discounted room rate, and a date range" do + expect(@block).must_be_kind_of Hotel::Block + end + + it "throws an exception if at least one of the rooms is unavailable for the given date range" do + rooms = (6..10).to_a + rate = 150.0 + date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + reservation = Hotel::Reservation.new( + id: 1, + room: 6, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_reservation(reservation) + + expect{@front_desk.reserve_block(rooms, rate, date_range)}.must_raise ArgumentError + end + + it "throws an exception if at least one of the rooms can be found in an existing hotel block for the given date range" do + rooms = (5..9).to_a + rate = 150.0 + date_range = Hotel::DateRange.new( + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + + expect{@front_desk.reserve_block(rooms, rate, date_range)}.must_raise ArgumentError + end + + it "adds the new Block to the blocks array" do + expect(@front_desk.blocks.length).must_equal 1 + expect(@front_desk.blocks.first).must_equal @block + end + end + + describe "#find_available_room_in_block" do + before do + @block = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_block(@block) + + @reservation = Hotel::Reservation.new( + id: 1, + room: 1, + block: 1, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_reservation(@reservation) + + @available_rooms = @front_desk.find_available_room_in_block(1) + end + + it "returns an array of valid room numbers" do + expect(@available_rooms).must_be_kind_of Array + expect(@available_rooms.first).must_be_kind_of Integer + expect(@block.rooms).must_include @available_rooms.first + expect(@block.rooms).must_include @available_rooms.last + end + + it "returns a list of rooms that are not reserved for a given date range" do + expect(@available_rooms.length).must_equal 4 + expect(@available_rooms).wont_include 1 + end + + it "returns all rooms if there are no Reservations with the given block id" do + @front_desk.reservations.clear + available_rooms = @front_desk.find_available_room_in_block(1) + + expect(@front_desk.reservations.length).must_equal 0 + expect(available_rooms).must_equal @block.rooms + end + + it "throws an exception if there are no rooms available" do + reservation2 = Hotel::Reservation.new( + id: 2, + room: 2, + block: 1, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + reservation3 = Hotel::Reservation.new( + id: 3, + room: 3, + block: 1, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + reservation4 = Hotel::Reservation.new( + id: 4, + room: 4, + block: 1, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + reservation5 = Hotel::Reservation.new( + id: 5, + room: 5, + block: 1, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_reservation(reservation2) + @front_desk.add_reservation(reservation3) + @front_desk.add_reservation(reservation4) + @front_desk.add_reservation(reservation5) + + expect(@front_desk.reservations.length).must_equal 5 + expect{@front_desk.find_available_room_in_block(1)}.must_raise ArgumentError + end + end + + describe "#reserve_room_in_block" do + before do + @block = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_block(@block) + + @reservation1 = Hotel::Reservation.new( + id: 1, + room: 1, + block: 1, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_reservation(@reservation1) + + @reservation2 = @front_desk.reserve_room_in_block(1, 2) + end + + it "can reserve a specific room from a hotel block" do + expect(@reservation2).must_be_kind_of Hotel::Reservation + expect(@block.rooms).must_include @reservation2.room + end + + it "assigns a unique id number to the new reservation" do + expect(@reservation2.id).must_equal 2 + end + + it "adds the new reservation to the reservations array" do + expect(@front_desk.reservations.length).must_equal 2 + expect(@front_desk.reservations.first).must_equal @reservation1 + expect(@front_desk.reservations.last).must_equal @reservation2 + end + end +end \ No newline at end of file diff --git a/test/reservation_test.rb b/test/reservation_test.rb new file mode 100644 index 000000000..16afa24c8 --- /dev/null +++ b/test/reservation_test.rb @@ -0,0 +1,62 @@ +require_relative 'test_helper' + +describe "Reservation class" do + before do + @reservation = Hotel::Reservation.new( + id: 1, + room: 15, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + end + + describe "Reservation instantiation" do + 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, :block, :rate, :date_range].each do |attribute| + expect(@reservation).must_respond_to attribute + end + + expect(@reservation.id).must_be_kind_of Integer + expect(@reservation.room).must_be_kind_of Integer + expect(@reservation.block).must_equal false + expect(@reservation.rate).must_be_kind_of Float + expect(@reservation.date_range).must_be_kind_of Hotel::DateRange + end + + it "throws an exception if the custom rate cannot be converted to a float" do + expect{Hotel::Reservation.new( + id: 1, + room: 15, + rate: "a", + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + )}.must_raise ArgumentError + end + end + + describe "#total_cost" do + it "accurately calculates the total cost for single rooms with the default rate" do + expect(@reservation.total_cost).must_equal 600.0 + end + + it "accurately calculates the total cost for a room reserved from a hotel block" do + @front_desk = Hotel::FrontDesk.new + block = Hotel::Block.new( + id: 1, + rooms: (1..5).to_a, + rate: 150.0, + start_date: Date.new(2020,3,2), + end_date: Date.new(2020,3,5) + ) + @front_desk.add_block(block) + + reservation = @front_desk.reserve_room_in_block(1, 1) + + expect(reservation.total_cost).must_equal 450.0 + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..68ef1c108 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,8 +1,19 @@ -# Add simplecov -require "minitest" -require "minitest/autorun" -require "minitest/reporters" +require 'simplecov' +SimpleCov.start do + add_filter 'test/' # Tests should not be checked for coverage +end + +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/skip_dsl' Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # require_relative your lib files here! +require_relative '../lib/reservation' +require_relative '../lib/front_desk' +require_relative '../lib/date_range' +require_relative '../lib/block' + +# In other test files, we will require_relative 'test_helper' \ No newline at end of file