diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..8e2afdc6c Binary files /dev/null and b/.DS_Store differ diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..0762c04d4 --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,35 @@ +module Hotel + class DateRange + attr_reader :start_date, :end_date + attr_accessor :dates + + def initialize(start_date:, end_date:) + @start_date = start_date + @end_date = end_date + @dates = Array(@start_date .. @end_date) + + # User: I want an exception raised when an invalid date range is provided, so that I can't make a reservation for an invalid date range + if @end_date < @start_date || @start_date == @end_date + raise ArgumentError.new("Your check-out date must be after your check-in.") + end + end + + def overlap?(daterange_instance) + overlap = !(daterange_instance.start_date >= end_date || daterange_instance.end_date <= start_date) + return overlap + end + + def include?(date) + if date >= @start_date && date < @end_date + return true + else + return false + end + end + + # calculate the number of nights for a reservation (end date is not a night) + def nights + return @dates.length - 1 + end + end +end diff --git a/lib/front_desk.rb b/lib/front_desk.rb new file mode 100644 index 000000000..ab3245d12 --- /dev/null +++ b/lib/front_desk.rb @@ -0,0 +1,105 @@ +require_relative 'reservation' +require_relative 'date_range' + +module Hotel + class FrontDesk + attr_accessor :reservations, :rooms, :calendar + + def initialize + @rooms = (1..20).map { |i| i } + @reservations = [] + @calendar = {} + end + + def populate_calendar(reservation) + temp = reservation.date_range.dates + temp.pop + temp.each do |date| + if !@calendar.key?(date) + @calendar[date] =[] + end + @calendar[date] << reservation + end + return @calendar + end + + # User: I can access the list of all of the rooms in the hotel + def list_rooms + return @rooms + end + + # User: I can make a reservation of a room for a given date range, and that room will not be part of any other reservation overlapping that date range + def reserve_room(requested_dates) + available_rooms = available_rooms(requested_dates) + + book_room = available_rooms.first + new_reservation = Hotel::Reservation.new( + room: book_room, + date_range: requested_dates, + ) + @reservations.push(new_reservation) + populate_calendar(new_reservation) + return new_reservation + end + + # User: I can access the list of reservations for a specific date, so that I can track reservations by date + def date_reservations(date) + date_resv = @calendar[date] + return date_resv + end + + # User: I access the list of reservations for a specified room and a given date range + # I split this user story into a handful of separate methods to perform different parts of the request + + # returns an array of reservations for requested dates + def range_reservations(requested_dates) + range_resv = [] + requested_dates.dates.each do |date| + if @calendar.key?(date) + range_resv.push(*@calendar[date]) + end + end + range_resv = range_resv.uniq + + return range_resv + end + + # search an array of reservations and return only the reservations for the specified room + def find_room_res(room_num, range_resv) + room_res = [] + range_resv.each do |res| + if res.room == room_num + room_res.push(res) + end + end + room_res = room_res.uniq + return room_res + end + + # User: I can view a list of rooms that are not reserved for a given date range, so that I can see all available rooms for that day + def available_rooms(requested_dates) + occupied_rooms = [] + select_res = range_reservations(requested_dates) + + select_res.each do |reservation| + occupied_rooms << reservation.room + end + + empty_rooms = [] + @rooms.each do |room| + unless occupied_rooms.include?(room) + empty_rooms << room + end + end + empty_rooms = empty_rooms.uniq + + # User: I want an exception raised if I try to reserve a room during a date range when all rooms are reserved, so that I cannot make two reservations for the same room that overlap by date + if empty_rooms.length > 0 + return empty_rooms + else + raise ArgumentError.new("Sorry, there are no rooms available. Please try other dates.") + end + + end + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..eeb871992 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,20 @@ +require_relative "date_range" + +module Hotel + class Reservation + attr_reader :room, :date_range + + def initialize(date_range:, room: nil) + @room = room + @date_range = date_range + + end + + # User: I can get the total cost for a given reservation + def cost + room_rate = 200 + cost = @date_range.nights * room_rate + return cost + end + end +end diff --git a/refactor.txt b/refactor.txt new file mode 100644 index 000000000..3656dc47f --- /dev/null +++ b/refactor.txt @@ -0,0 +1,36 @@ +TO DO: + +Front Desk: + +date_reservations +* Add raise argument error if date is not a date +* Add raise argument error if there are no reservations for the date + +range_reservations +* Add raise argument error if date range is not a date range +* Add raise argument error if there are no reservations for the date range + +available_rooms +* argument error if no rooms available isn't working in test + +Front Desk Test: + +available_rooms +* argument error if no rooms available isn't working in test + +reserve_room +* add more nominal and edge tests + +date_reservations +* add more nominal and edge tests + +range_reservations +* add more nominal and edge tests + +find_room_res +* add more nominal and edge tests + +available_rooms +* raise argument error test not working - currently skipped + + diff --git a/test/date_range_test.rb b/test/date_range_test.rb new file mode 100644 index 000000000..7470076c8 --- /dev/null +++ b/test/date_range_test.rb @@ -0,0 +1,162 @@ +require_relative "test_helper" + + +describe Hotel::DateRange do + before do + @start_date = Date.new(2021, 01, 01) + @end_date = Date.new(2021, 01, 03) + @start_date2 = Date.new(2021, 01, 01) + @end_date2 = Date.new(2021, 01, 02) + @range_inst = Hotel::DateRange.new(start_date: @start_date, end_date: @end_date) + @range_inst2 = Hotel::DateRange.new(start_date: @start_date2, end_date: @end_date2) + end + + describe "constructor" do + it "is an instance of DateRange" do + expect(@range_inst).must_be_kind_of Hotel::DateRange + end + + it "start_date is a Date" do + expect(@range_inst.start_date).must_be_kind_of Date + end + + it "end_date is a Date" do + expect(@range_inst.end_date).must_be_kind_of Date + end + + it "dates is an array" do + expect(@range_inst.dates).must_be_kind_of Array + end + + it "can be initialized with two dates" do + expect(@range_inst.start_date).must_equal @start_date + expect(@range_inst.end_date).must_equal @end_date + end + + it "raises an error if the end date is before the start date" do + expect { Hotel::DateRange.new( + start_date: Date.new(2021, 01, 01), + end_date: Date.new(2020, 12, 31) + ) }.must_raise ArgumentError + end + + it "raises an error if the end date is the same as the start date" do + expect { Hotel::DateRange.new( + start_date: Date.new(2021, 01, 01), + end_date: Date.new(2021, 01, 01) + ) }.must_raise ArgumentError + end + end + + describe "overlap?" do + it "returns true for the same range" do + start_date = Date.new(2021, 01, 01) + end_date = Date.new(2021, 01, 03) + test_range = Hotel::DateRange.new( + start_date: start_date, + end_date: end_date + ) + + expect(@range_inst.overlap?(test_range)).must_equal true + end + + it "returns true for a contained range" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2021, 01, 01), + end_date: Date.new(2021, 01, 02) + ) + + expect(@range_inst.overlap?(test_range)).must_equal true + end + + it "returns true for a range that overlaps in front" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2020, 12, 01), + end_date: Date.new(2021, 01, 02) + ) + + expect(@range_inst.overlap?(test_range)).must_equal true + end + + it "returns true for a range that overlaps in the back" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2021, 1, 02), + end_date: Date.new(2021, 01, 06) + ) + + expect(@range_inst.overlap?(test_range)).must_equal true + end + + it "returns true for a containing range" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2020, 12, 01), + end_date: Date.new(2021, 01, 12) + ) + + expect(@range_inst.overlap?(test_range)).must_equal true + end + + it "returns false for a range starting on the end_date date" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2021, 01, 03), + end_date: Date.new(2021, 01, 06) + ) + + expect(@range_inst.overlap?(test_range)).must_equal false + end + + it "returns false for a range ending on the start_date date" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2020, 12, 03), + end_date: Date.new(2021, 01, 01) + ) + + expect(@range_inst.overlap?(test_range)).must_equal false + end + + it "returns false for a range completely before" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2020, 01, 03), + end_date: Date.new(2020, 01, 06) + ) + + expect(@range_inst.overlap?(test_range)).must_equal false + end + + it "returns false for a date completely after" do + test_range = Hotel::DateRange.new( + start_date: Date.new(2022, 01, 03), + end_date: Date.new(2022, 01, 06) + ) + + expect(@range_inst.overlap?(test_range)).must_equal false + end + end + + describe "include?" do + it "returns false if the date is clearly out" do + date = Date.new(2022,1,1) + expect(@range_inst.include?(date)).must_equal false + end + + it "returns true for dates in the range" do + date = @start_date + expect(@range_inst.include?(date)).must_equal true + end + + it "returns false for the end_date date" do + date = @end_date + expect(@range_inst.include?(date)).must_equal false + end + end + + describe "nights" do + it "returns the correct number of nights" do + expect(@range_inst.nights).must_equal 2 + end + + it "returns a number" do + expect(@range_inst.nights).must_be_kind_of Numeric + end + end +end diff --git a/test/front_desk_test.rb b/test/front_desk_test.rb new file mode 100644 index 000000000..ae8ed48d6 --- /dev/null +++ b/test/front_desk_test.rb @@ -0,0 +1,144 @@ +require_relative "test_helper" + +describe Hotel::FrontDesk do + before do + @desk_instance = Hotel::FrontDesk.new + @date_instance = Hotel::DateRange.new( + start_date: Date.new(2021, 01, 01), + end_date: Date.new(2021, 01, 03) + ) + @date_instance2 = Hotel::DateRange.new( + start_date: Date.new(2021, 01, 02), + end_date: Date.new(2021, 01, 03) + ) + @date_instance3 = Hotel::DateRange.new( + start_date: Date.new(2021, 04, 12), + end_date: Date.new(2021, 04, 13) + ) + @date_instance4 = Hotel::DateRange.new( + start_date: Date.new(2021, 02, 12), + end_date: Date.new(2021, 02, 19) + ) + @res_instance = Hotel::Reservation.new( + date_range: @date_instance, + room: 2 + ) + @room_list = @desk_instance.available_rooms(@date_instance) + @reserved = @desk_instance.reserve_room(@date_instance) + @range_res = @desk_instance.range_reservations(@date_instance) + @room_res = @desk_instance.find_room_res(2, @range_res) + end + + describe "initialize" do + it "is an instance of FrontDesk" do + expect(@desk_instance).must_be_kind_of Hotel::FrontDesk + end + + it "reservations is a type of Array" do + expect(@desk_instance.reservations).must_be_kind_of Array + end + + it "calendar is a type of Hash" do + expect(@desk_instance.calendar).must_be_kind_of Hash + end + + it "calendar is a type of Hash" do + expect(@desk_instance.calendar).must_be_kind_of Hash + end + + it "rooms is a type of array" do + expect(@desk_instance.rooms).must_be_kind_of Array + end + + it "rooms array has 20 items" do + expect(@desk_instance.rooms.length).must_equal 20 + end + end + + describe "list_rooms" do + it "takes date range and returns a list" do + expect(@desk_instance.list_rooms).must_be_kind_of Array + end + end + + describe "populate_calendar" do + before do + @cal_instance = @desk_instance.populate_calendar(@res_instance) + end + + it "takes a reservation and loops through date ranges" do + @desk_instance.populate_calendar(@res_instance) + + @res_instance.date_range.dates.each do |date| + date.must_be_kind_of Date + end + end + + it "returns a hash" do + expect(@cal_instance).must_be_kind_of Hash + end + + it "creates the same number of hash keys as there are nights in the range" do + expect(@cal_instance.length).must_equal 2 + end + end + + describe "reserve_room" do + it "creates a Reservation" do + expect(@reserved).must_be_kind_of Hotel::Reservation + end + + it "can create multiple reservations" do + # the first reservation was added in before + @desk_instance.reserve_room(@date_instance2) + @desk_instance.reserve_room(@date_instance3) + @desk_instance.reserve_room(@date_instance4) + + expect(@desk_instance.reservations.length).must_equal 4 + end + end + + describe "date_reservations" do + before do + date = Date.new(2021, 01, 01) + @date_res = @desk_instance.date_reservations(date) + end + + it "returns an array of reservations for a single date" do + expect(@date_res).must_be_kind_of Array + expect(@date_res[0]).must_be_kind_of Hotel::Reservation + end + end + + describe "range_reservations" do + it "returns an array of reservations for a range of reservations" do + expect(@range_res).must_be_kind_of Array + expect(@range_res[0]).must_be_kind_of Hotel::Reservation + end + end + + describe "find_room_res" do + it "returns a list of reservations" do + Hotel::Reservation.new( + date_range: @date_instance, + room: 2 + ) + @room_res = @desk_instance.find_room_res(2, @range_res) + + expect(@room_res).must_be_kind_of Array + end + end + + describe "available_rooms" do + it "takes date range and returns a list of numbers" do + expect(@room_list).must_be_kind_of Array + expect(@room_list[0]).must_be_kind_of Integer + end + + xit "raises an argument error if no rooms available" do + 20.times { @desk_instance.reserve_room(@date_instance)} + + expect{@desk_instance.available_rooms(@date_instance)}.must_raise ArgumentError + 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..c09cc751f --- /dev/null +++ b/test/reservation_test.rb @@ -0,0 +1,27 @@ +require_relative "test_helper" + +describe Hotel::Reservation do + before do + date_instance = Hotel::DateRange.new( + start_date: Date.new(2021, 01, 01), + end_date: Date.new(2021, 01, 04) + ) + + @res_instance = Hotel::Reservation.new( + date_range: date_instance, + room: 2 + ) + end + + describe "initialize" do + it "is an instance of Reservation" do + expect(@res_instance).must_be_kind_of Hotel::Reservation + end + end + + describe "cost" do + it "returns a number" do + expect(@res_instance.cost).must_be_kind_of Numeric + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..a593c1250 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,8 +1,17 @@ -# Add simplecov +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" + +require "date" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new -# require_relative your lib files here! +require_relative "../lib/front_desk" +require_relative "../lib/reservation" +require_relative "../lib/date_range"