diff --git a/.gitignore b/.gitignore index 5e1422c9c..3f1d8f765 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ /test/tmp/ /test/version_tmp/ /tmp/ +.gemfile + +.DS_Store # Used by dotenv library to load environment variables. # .env 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..b8c1f7902 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,38 @@ +#### What classes does each implementation include? Are the lists the same? +Both implementations have the same list of classes: CartEntry, ShoppingCart, and Order + +#### Write down a sentence to describe each class. +CartEntry: A product with its quantity +ShoppingCart: A list of products +Order: Manages the checkout of the ShoppingCart + +#### How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. +CartEntry < ShoppingCart < Order +An order has a shopping cart and a shopping cart has many cart entries + +#### What data does each class store? How (if at all) does this differ between the two implementations? +In Implementation A, both the CartEntry and the ShoppingCart contain the data associated with each instance. The Order class calculates the cost of all the CartEntries in the ShoppingCart. + +In Implementation B, the CartEntry is responsible for determining its gross price. The ShoppingCart is also responsible for determining the subtotal of the cart (sum of CartEntries' prices). The Order takes the cart subtotal and calculates the total by adding sales tax. + +#### What methods does each class have? How (if at all) does this differ between the two implementations? +Both implementations have a total_price method in the Order class, but in Implementation A, the Order class is the only class to calculate costs/prices. + +In Implementation B, each class is responsible for calculating its cost. + +#### 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?
+ The price computation is retained in Order in Implementation A, in Implementation B, it is delegated to each lower level class. + +- Does total_price directly manipulate the instance variables of other classes?
+ In A, the data is accessed by the accessor method, but Order#total_price has to know about the accessor method (and thusly, the instance variable) for the CartEntry.
+ In B, each price method of the higher level classes calls the price method of the class directly below it. + +#### If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? +If bulk pricing is desired, each item would need a minimum quantity to activate bulk price and would need the unit pricing for the item when bulk. This would change the calculation of price for each CartEntry. Implementation B would be easier to modify because the bulk pricing could be set or saved in CartEntry and none of the higher level classes would need to change their price method. + +#### Which implementation better adheres to the single responsibility principle? +Implementation B + +#### Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? +Implementation B diff --git a/hotel-revisited.md b/hotel-revisited.md new file mode 100644 index 000000000..4e4df2e89 --- /dev/null +++ b/hotel-revisited.md @@ -0,0 +1,37 @@ +## What is the responsibility of each class? + +- **Room**: check if the room is available + +- **Reservation**: calculates the cost of the reservation + +- **Block**: validate the number of rooms that are to be reserved for the block + +- **Admin**: manage rooms + + Reference implementation methods: + + - reserve + - list reservations + - available rooms (includes block) + - build block + - reserve from block + + My implementation + + - new reservation + - list reservations + - calculate reservation cost + - find reservation + - find available rooms + - reserve block + - get available block rooms + - helpers: + - get rooms (initialize with a certain number of rooms) + - specified room check + - check for reservation + - find block + + +## Where does a class directly modify the attributes of another class? + +- find_block finds based on attributes (does not modify) diff --git a/lib/admin.rb b/lib/admin.rb new file mode 100644 index 000000000..5db04cf60 --- /dev/null +++ b/lib/admin.rb @@ -0,0 +1,138 @@ +module Hotel + class Admin + attr_reader :rooms, :reservations, :blocks + + def initialize(num_of_rooms=20) # so that a new Admin can be instantiated for a different hotel with a diff number of rooms + @rooms = get_rooms(num_of_rooms) + @reservations = [] + @blocks = [] + end + + def new_reservation(start_date:, end_date:, guest_last_name:, block_last_name: nil, cost: STANDARD_RATE, room_id: nil) + start_date = DateHelper.parse(:start_date) + end_date = DateHelper.parse(:end_date) + block_last_name = :block_last_name + + + available_room = find_available_rooms(start_date, end_date, block_last_name: block_last_name).first + + if block_last_name + input[:cost] = find_block(start_date, block_last_name).cost + end + + specified_room_check(start_date, room_id) + + new_details = {room_id: available_room.room_id, room: available_room}.merge(input) # this should let you specify a room and room_id + new_reservation = Reservation.new(new_details) + + @reservations << new_reservation + available_room.add_reservation(new_reservation) + + return new_reservation + end + + def list_reservations(start_date) + start_date = DateHelper.parse(start_date) + + date_reservations = @reservations.select do |reservation| + (reservation.start_date..reservation.end_date).include?(start_date) + end + return date_reservations.empty? ? nil : date_reservations + end + + def calculate_reservation_cost(start_date:, room_id:) + reservation = check_for_reservation(find_reservation(start_date: start_date, room_id: room_id)) + return reservation.calculate_cost + end + + def find_reservation(start_date:, room_id:) + start_date = DateHelper.parse(start_date) + found_reservation = @reservations.find(nil) do |reservation| + reservation.start_date == start_date && reservation.room_id == room_id + end + return found_reservation + end + + def find_available_rooms(start_date, end_date, block_last_name: nil) + available_rooms = @rooms.select { |room| room.check_availability(start_date, end_date, block_last_name: block_last_name) == :AVAILABLE } + + if available_rooms.empty? + raise NoAvailableRoom.new("No rooms are available for those dates") + end + + return available_rooms + end + + def reserve_block(input) + start_date = input[:start_date] + end_date = input[:end_date] + room_count = input[:room_count] + + available_rooms = find_available_rooms(start_date, end_date) + + if available_rooms.length < room_count + raise NoAvailableRoom.new("Not enough rooms for this block, only #{available_rooms.length} rooms available.") + end + + rooms_to_block = available_rooms.first(room_count) + block_details = input.merge( { block_rooms: rooms_to_block }) + new_block = Block.new(block_details) + @blocks << new_block + new_block.block_rooms.each do |room| + room.add_block(new_block) + end + + return new_block + end + + def get_available_blockrooms(input) + start_date = DateHelper.parse(input[:start_date]) + end_date = DateHelper.parse(input[:end_date]) + block_last_name = input[:block_last_name] + + sought_block = find_block(start_date, block_last_name) + + if sought_block.nil? + raise NoReservation.new("This is not a reserved block") + end + + available_rooms = sought_block.block_rooms.select do |room| + room.check_availability(start_date, end_date, block_last_name: block_last_name) == :AVAILABLE + end + return available_rooms.empty? ? nil : available_rooms + end + + private + + def get_rooms(num_of_rooms) # factory method + rooms = [] + num_of_rooms.times do |i| + input = {id: (i + 1)} + rooms << Room.new(input) + end + return rooms + end + + def specified_room_check(start_date, room_id) + if room_id + checking_reservation = find_reservation(start_date: start_date, room_id: room_id) + raise NoAvailableRoom.new("This room is already reserved.") unless checking_reservation.nil? + end + end + + def check_for_reservation(reservation) + if reservation.nil? + raise NoReservation.new("There is no reservation for that date") + end + return reservation + end + + def find_block(start_date, block_last_name) + found_block = @blocks.find do |block| + block.start_date == start_date && block.block_last_name == block_last_name + end + return found_block + end + + end +end diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..0ca2b3061 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,25 @@ +module Hotel + class Block + attr_reader :start_date, :end_date, :block_rooms, :cost, :block_last_name + + def initialize(input) + @cost = input[:cost].nil? ? STANDARD_RATE : input[:cost] + @start_date = DateHelper.parse(input[:start_date]) + @end_date = DateHelper.parse(input[:end_date]) + @block_rooms = check_room_count(input[:block_rooms]) # wouldn't I want this to take room_id's not rooms? or will the rooms be found in admin + @block_last_name = input[:block_last_name] + + if @block_last_name.nil? + raise ArgumentError.new("Must enter a last name") + end + end + + def check_room_count(block_rooms) + if block_rooms.length > 5 + raise StandardError.new("Invalid number of rooms: #{block_rooms.length}. A block can only have up to 5 rooms.") + end + return block_rooms + end + + end +end diff --git a/lib/date_helper.rb b/lib/date_helper.rb new file mode 100644 index 000000000..264497157 --- /dev/null +++ b/lib/date_helper.rb @@ -0,0 +1,26 @@ +module Hotel + class DateHelper + def self.parse(date) + case + when date.instance_of?(Date) + return date + when date.match(/^\d{2,4}-\d{1,2}-\d{1,2}$/) + Date.parse(date) + when date.match(/^\d{1,2}-\d{1,2}-\d{4}$/) + Date.strptime(date, '%m-%d-%Y') + when date.match(/^\d{1,2}\/\d{1,2}\/\d{4}$/) + Date.strptime(date, '%m/%d/%Y') + when date.match(/^\d{6,8}$/) + Date.parse(date) + end + end + + + def self.overlap_date_range?(start_date, end_date, block_res) + reservation_range = (block_res.start_date...block_res.end_date).to_a + check_range = (parse(start_date)...parse(end_date)).to_a + overlap = reservation_range & check_range + return !overlap.empty? + end + end # class +end # module diff --git a/lib/notavailable.rb b/lib/notavailable.rb new file mode 100644 index 000000000..2ae8d03dc --- /dev/null +++ b/lib/notavailable.rb @@ -0,0 +1,5 @@ +class NoAvailableRoom < StandardError +end + +class NoReservation < StandardError +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..aa8c788ab --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,33 @@ +module Hotel + class Reservation + attr_reader :room, :start_date, :end_date, :guest_last_name, :room_id, :cost + + def initialize(input) + @start_date = DateHelper.parse(input[:start_date]) + @end_date = DateHelper.parse(input[:end_date]) + @room = input[:room].nil? ? nil : input[:room] + @room_id = input[:room_id] + @guest_last_name = input[:guest_last_name] + @guest_first_name = input[:guest_first_name].nil? ? nil : input[:guest_first_name] + @cost = input[:cost] || STANDARD_RATE + + if (@start_date == nil || @end_date == nil) || @start_date > @end_date + raise StandardError.new("Invalid dates") + end + + if @guest_last_name.nil? + raise StandardError.new("Must enter a last name") + end + + if @room_id.nil? + raise StandardError.new("Must enter a room number") + end + end + + def calculate_cost + duration = (@end_date - @start_date).to_i + return (duration * @cost).to_f.round(2) + end + + end # Reservation +end # Hotel diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..63b249f9d --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,64 @@ +module Hotel + STANDARD_RATE = 200 + + class Room + attr_reader :room_id, :reservations, :blocks + + def initialize(input) # taking a hash so that an array of reservations can be loaded if that's what user wants to do + @room_id = input[:id] + @reservations = input[:reservations] == nil ? [] : input[:reservations] # setting the default to an empty array if no reservations for that room + @blocks = [] + end + + def check_availability(start_date, end_date, block_last_name: nil) # should this be a date or string instance? + start_date = DateHelper.parse(start_date) + end_date = DateHelper.parse(end_date) + + # if there already is a reservation, then this room is not available + @reservations.each do |reservation| + if DateHelper.overlap_date_range?(start_date, end_date, reservation) + return :UNAVAILABLE + end + end + + check_for_block(start_date, end_date, block_last_name: block_last_name) + end + + def add_reservation(reservation) + @reservations << reservation + end + + def add_block(block) + @blocks << block + end + + private + + def check_for_block(start_date, end_date, block_last_name: nil) + + if block_last_name.nil? + overlapping_blocks = @blocks.select do |block| + DateHelper.overlap_date_range?(start_date, end_date, block) + end + + if overlapping_blocks.empty? + return :AVAILABLE + else + return :UNAVAILABLE + # raise NoAvailableRoom.new("This room is not available for reserving") + end + + else + selected_block = @blocks.find {|block| block.start_date == start_date && block.block_last_name == block_last_name} + + if selected_block.nil? + return :UNAVAILABLE + else + return :AVAILABLE + end + + end + end + + end # class +end # module diff --git a/specs/admin_spec.rb b/specs/admin_spec.rb new file mode 100644 index 000000000..37be4ac81 --- /dev/null +++ b/specs/admin_spec.rb @@ -0,0 +1,308 @@ +require_relative 'spec_helper' + +describe "Hotel::Admin" do + + describe "Admin#initalize" do + + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + end + + it "should have a collection of 20 rooms" do + @admin.must_respond_to :rooms + @admin.rooms.must_be_instance_of Array + @admin.rooms.length.must_equal @number_of_rooms + @admin.rooms.each do |room| + room.must_be_instance_of Hotel::Room + end + end + + it "should have a collection of reservations" do + @admin.must_respond_to :reservations + @admin.reservations.must_be_instance_of Array + end + end + + describe "Admin#new_reservation" do + + before do + number_of_rooms = 20 + @admin = Hotel::Admin.new(number_of_rooms) + @input = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + end + + let (:new_booking) { + {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + } + let (:block_details) { + {room_count: 4, start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace"} + } + + it "should create a new instance of reservation if the reservation is available" do + new_reservation = @admin.new_reservation(@input) + new_reservation.must_be_instance_of Hotel::Reservation + end + + it "should not be able to reserve a room in a block, if not associated with the block" do + 4.times do + @admin.reserve_block(block_details) # rooms 1-16 reserved + end + all_blocked_rooms = @admin.blocks.map {|block| block.block_rooms}.flatten + all_block_ids = all_blocked_rooms.map {|room| room.room_id} + new_reservation = @admin.new_reservation(new_booking) + new_res_id = new_reservation.room_id + new_res_id.must_equal 17 + all_block_ids.wont_include new_res_id + + end + + it "should be able to reserve a room in a block if associated with a block" do + @admin.reserve_block(block_details) # rooms 1-4 + + reservation_details = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper", room_id: 1, block_last_name: "Lovelace"} + new_reservation = @admin.new_reservation(reservation_details) + room1 = @admin.rooms.find {|room| room.room_id == reservation_details[:room_id]} + + @admin.reservations.must_include new_reservation + room1.reservations.must_include new_reservation + + end + + it "should be able to reserve a room that has another reservation ending on the start date" do + first_reservation = @admin.new_reservation(@input) # room 1 + + new_reservation = {start_date: "2018-03-08", end_date: "2018-03-10", guest_last_name: "Franklin", room_id: 1} + reservation = @admin.new_reservation(new_reservation) + reservation.room_id.must_equal first_reservation.room_id + room1 = @admin.rooms.find {|room| room.room_id == 1} + room1.reservations.must_include first_reservation + room1.reservations.must_include reservation + end + + it "should raise an error if there are no available rooms for that date" do + 20.times do + @admin.new_reservation(@input) + end + proc{@admin.new_reservation(@input)}.must_raise NoAvailableRoom + end + + it "should add the reservation to the room's list of reservations" do + new_reservation = @admin.new_reservation(@input) + booked_room = new_reservation.room + booked_room.reservations.must_include new_reservation + end + + end + + describe "Admin#list_reservations" do + + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @input1 = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + @input2 = {start_date: "2018-03-07", end_date: "2018-03-09", guest_last_name: "Hopper"} + @input3 = {start_date: "2018-04-05", end_date: "2018-04-09", guest_last_name: "Hopper"} + @reservation1 = @admin.new_reservation(@input1) + @reservation2 = @admin.new_reservation(@input2) + @reservation3 = @admin.new_reservation(@input3) + end + + it "should return a collection" do + my_reservations = @admin.list_reservations("2018-03-07") + my_reservations.must_be_instance_of Array + end + + it "should return a collection whose items are an instance of Reservation" do + my_reservations = @admin.list_reservations("2018-03-07") + my_reservations.each do |reservation| + reservation.must_be_instance_of Hotel::Reservation + end + end + + it "should only return reservations that are booked during that date" do + my_reservations = @admin.list_reservations("2018-03-07") + my_reservations.first.room_id.must_equal @reservation1.room_id + my_reservations.last.room_id.must_equal @reservation2.room_id + my_reservations.wont_include @reservation3 + end + + it "should return nil if no reservations for that date" do + @admin.list_reservations("2018-03-12").must_be_nil + end + end + + describe "Admin#calculate_reservation_cost" do + + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @input1 = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + @reservation1 = @admin.new_reservation(@input1) + end + + it "should calculate the cost for a given reservation" do + cost = @admin.calculate_reservation_cost(room_id: @reservation1.room_id, start_date: "2018-03-05") + cost.must_be_instance_of Float + cost.must_equal 600.00 + end + + it "should return an error if no reservation" do + proc{@admin.calculate_reservation_cost(room_id: 7, start_date: "2018-03-05")}.must_raise NoReservation + end + + it "should calculate the cost based on the block pricing, if block pricing is specified" do + block_input = {room_count: 4, start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace", cost: 150} + @admin.reserve_block(block_input) + + reservation_deets = {start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace", guest_last_name: "Franklin"} + new_reservation = @admin.new_reservation(reservation_deets) + room_id = new_reservation.room_id + + cost = @admin.calculate_reservation_cost(room_id: room_id, start_date: reservation_deets[:start_date]) + cost.must_be_instance_of Float + cost.must_equal 450.00 + end + end + + describe "Admin#find_available_rooms" do + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @input1 = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + @rooms_to_reserve = 5 + @rooms_to_reserve.times do + @admin.new_reservation(@input1) + end + end + + it "must return a list of rooms that are available for booking during the dates specified" do + available_rooms = @admin.find_available_rooms("2018-03-05", "2018-03-08") + available_rooms.must_be_instance_of Array + available_rooms.each do |room| + room.must_be_instance_of Hotel::Room + end + available_rooms.length.must_equal @admin.rooms.length - @rooms_to_reserve + end + + it "must raise an error if there are no available rooms" do + rooms_to_reserve = (@number_of_rooms - @rooms_to_reserve) + rooms_to_reserve.times do + @admin.new_reservation(@input1) + end + proc {@admin.new_reservation(@input1)}.must_raise NoAvailableRoom + end + end + + describe "Admin#reserve_block" do + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @input1 = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + @rooms_to_reserve = 5 + @rooms_to_reserve.times do + @admin.new_reservation(@input1) + end + end + + let (:block_input) { + {room_count: 4, start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace"} + } + + it "should create a new instance of Hotel::Block" do + new_block = @admin.reserve_block(block_input) + new_block.must_be_instance_of Hotel::Block + end + + it "should raise an error if there are not enough rooms for the block" do + rooms_to_reserve = 12 + rooms_to_reserve.times do + @admin.new_reservation(@input1) + end + + proc {@admin.reserve_block(block_input)}.must_raise NoAvailableRoom + end + + it "should add the block to a room's list of blocks" do + new_block = @admin.reserve_block(block_input) + new_block.block_rooms.each do |room| + room.blocks.must_include new_block + end + end + + it "should not block the same rooms for a new block" do + block1 = @admin.reserve_block(block_input) # rooms 6 - 9 + block1_rmids = block1.block_rooms.map {|room| room.room_id} + + new_block_deets = {room_count: 4, start_date: "2018-03-07", end_date: "2018-03-12", block_last_name: "Franklin", cost: 150} + new_block = @admin.reserve_block(new_block_deets) + new_block_rmids = new_block.block_rooms.map {|room| room.room_id} + + overlap = block1_rmids & new_block_rmids + overlap.must_equal [] + end + end + + describe "Admin#block" do + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @input = {room_count: 4, start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace"} + @new_block = @admin.reserve_block(@input) + end + + it "should have a collection of Blocks" do + @admin.blocks.must_be_instance_of Array + @admin.blocks.each do |block| + block.must_be_instance_of Hotel::Block + end + end + end + + describe "Admin#get_available_blockrooms" do + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @input = {room_count: 4, start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace"} + @new_block = @admin.reserve_block(@input) + end + + let (:reserv_input) { + {start_date: "2018-03-05", end_date: "2018-03-08", block_last_name: "Lovelace", guest_last_name: "Hopper"} + } + + let (:block_info) { + { block_last_name: "Lovelace", start_date: "2018-03-05", end_date: "2018-03-08" } + } + + it "should return a collection of rooms of a block that are still available" do + + new_reservation = @admin.new_reservation(reserv_input) + reserved_room_id = new_reservation.room_id + + available_blockrooms = @admin.get_available_blockrooms(block_info) + + available_blockrooms.must_be_instance_of Array + available_blockrooms.each do |room| + room.must_be_instance_of Hotel::Room + end + + available_blockrooms_ids = available_blockrooms.map { |room| room.room_id } + available_blockrooms_ids.wont_include reserved_room_id + end + + it "should return nil if there are no available rooms in the block" do + @input[:room_count].times do + @admin.new_reservation(reserv_input) + end + available_blockrooms = @admin.get_available_blockrooms(block_info) + + available_blockrooms.must_be_nil + end + + it "should raise an error if that is not a reserved block" do + not_a_block = { block_last_name: "Lovelace", start_date: "2018-03-06", end_date: "2018-03-08" } + proc{@admin.get_available_blockrooms(not_a_block)}.must_raise NoReservation + end + end +end diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..1cef700ad --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,38 @@ +require_relative 'spec_helper' + +describe 'Hotel::Block'do + + describe 'Block#initialize'do + before do + @number_of_rooms = 20 + @admin = Hotel::Admin.new(@number_of_rooms) + @block_rooms = @admin.rooms.select {|room| (1..5).include?(room.room_id)} + input = {start_date: "2018-03-08", end_date: "2018-03-12", block_rooms: @block_rooms, cost: 150, block_last_name: "Lovelace"} + @new_block = Hotel::Block.new(input) + end + + it "can be instantiated" do + @new_block.must_be_instance_of Hotel::Block + end + + it "has a list of rooms in the block" do + @new_block.block_rooms.must_be_instance_of Array + @new_block.block_rooms.each do |room| + room.must_be_instance_of Hotel::Room + end + end + + it "cannot have more than 5 rooms in the block" do + block_rooms = @admin.rooms.select {|room| (1..6).include?(room.room_id)} + input = {start_date: "2018-03-08", end_date: "2018-03-12", block_rooms: block_rooms, cost: 150} + + proc {Hotel::Block.new(input)}.must_raise StandardError + + end + + it "must raise an error if no last name is given for the block" do + input = {start_date: "2018-03-08", end_date: "2018-03-12", block_rooms: @block_rooms, cost: 150 } + proc{Hotel::Block.new(input)}.must_raise ArgumentError + end + end +end diff --git a/specs/date_helper_spec.rb b/specs/date_helper_spec.rb new file mode 100644 index 000000000..65b7591f4 --- /dev/null +++ b/specs/date_helper_spec.rb @@ -0,0 +1,55 @@ +require_relative 'spec_helper' + +describe Hotel::DateHelper do + + describe "DateHelper.parse" do + it "must take in a string and return an instance of date" do + date1 = "2018-03-05" + Hotel::DateHelper.parse(date1).must_be_instance_of Date + end + + it "can parse various versions of a date string" do + date1 = "05/03/2018" + date_1 = Hotel::DateHelper.parse(date1) + date_1.must_be_instance_of Date + date_1.year.must_equal 2018 + date_1.month.must_equal 5 + + date2 = "05-03-2018" + date_2 = Hotel::DateHelper.parse(date2) + date_2.must_be_instance_of Date + date_2.month.must_equal 5 + + date3 = "20180405" + date_3 = Hotel::DateHelper.parse(date3) + date_3.must_be_instance_of Date + date_3.month.must_equal 4 + date_3.day.must_equal 5 + end + + it "can handle an instance of date" do + date1 = Date.new(2018,03,05) + date_1 = Hotel::DateHelper.parse(date1) + date_1.must_be_instance_of Date + end + + it "will raise an error if the date entered is not a possible date" do + date1 = "2018-02-30" + proc {Hotel::DateHelper.parse(date1)}.must_raise ArgumentError + + end + end + + describe "DateHelper.overlap_date_range?" do + before do + @admin = Hotel::Admin.new(5) + @input = {start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Hopper"} + @reservation1 = @admin.new_reservation(@input) + end + + it "will return true if two date ranges overlap" do + Hotel::DateHelper.overlap_date_range?("2018-03-07", "2018-03-10", @reservation1).must_equal true + end + end + +end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..2d1435772 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,57 @@ +require_relative 'spec_helper' + +describe "Hotel::Reservation" do + describe "initialize" do + before do + @room = Hotel::Room.new({id: 2}) + details = { start_date: "2018-03-23", end_date: "2018-03-25", room_id: 2, guest_last_name: "Lovelace" } + @reservation = Hotel::Reservation.new(details) + end + + it "must be an instance of Reservation" do + @reservation.must_be_instance_of Hotel::Reservation + end + + it "must raise an error if given an invalid start or end date" do + details = { start_date: "2018-03-23", end_date: "2018-02-25", room_id: 2, guest_last_name: "Hopper"} + proc {Hotel::Reservation.new(details)}.must_raise StandardError + + details2 = { room_id: 2, guest_last_name: "Hopper" } + proc {Hotel::Reservation.new(details2)}.must_raise StandardError + end + + it "must raise an error if no last name is entered" do + details = { start_date: "2018-03-23", end_date: "2018-03-25", room_id: 2,} + proc {Hotel::Reservation.new(details)}.must_raise StandardError + end + + it "must raise an error if no room id is given" do + details = { start_date: "2018-03-23", end_date: "2018-03-25", guest_last_name: "Lovelace"} + proc {Hotel::Reservation.new(details)}.must_raise StandardError + end + + it "must have an associated room id" do + @reservation.must_respond_to :room_id + @reservation.room_id.must_equal @room.room_id + end + + it "must have a guest" do + @reservation.must_respond_to :guest_last_name + @reservation.guest_last_name.must_equal "Lovelace" + end + end + + describe "Reservation#calculate_cost" do + before do + details = { start_date: "2018-03-23", end_date: "2018-03-25", room_id: 2, guest_last_name: "Lovelace" } + @reservation = Hotel::Reservation.new(details) + end + + it "must return the cost of the reservation" do + @reservation.calculate_cost.must_be_instance_of Float + @reservation.calculate_cost.must_equal 400.00 + end + + end + +end diff --git a/specs/room_spec.rb b/specs/room_spec.rb new file mode 100644 index 000000000..b18d0b873 --- /dev/null +++ b/specs/room_spec.rb @@ -0,0 +1,52 @@ +require_relative 'spec_helper' + +describe "Hotel::Room" do + describe "initialize" do + before do + @new_room = Hotel::Room.new({id: 1}) + end + it "can be created" do + @new_room.must_be_instance_of Hotel::Room + end + + it "must have a room_id" do + @new_room.must_respond_to :room_id + @new_room.room_id.must_equal 1 + end + + it "should be able to return a list of its reservations" do + @new_room.reservations.must_be_kind_of Array + @new_room.add_reservation(Hotel::Reservation.new({start_date: "2018-03-05", end_date: "2018-03-08", guest_last_name: "Lovelace", room_id: 2})) + @new_room.reservations.each do |reservation| + reservation.must_be_instance_of Hotel::Reservation + end + end + end + + describe "Room#check_availability" do + before do + details = {start_date: "2018-03-03", end_date: "2018-03-08", room_id: 1, guest_last_name: "Hopper"} + new_reservation = Hotel::Reservation.new(details) + @new_room = Hotel::Room.new({id: 1, reservations: [new_reservation]}) + end + + it "should return UNAVAILABLE if a reservation is wanted during a booked date" do + start_date1 = "2018-03-05" + end_date1 = "2018-03-09" + status = @new_room.check_availability(start_date1, end_date1) + status.must_equal :UNAVAILABLE + + start_date2 = "2018-03-01" + end_date2 = "2018-03-07" + status = @new_room.check_availability(start_date2, end_date2) + status.must_equal :UNAVAILABLE + end + + it "should return AVAILABLE if a room is available during that time" do + start_date = "2018-03-09" + end_date = "2018-03-12" + status = @new_room.check_availability(start_date, end_date) + status.must_equal :AVAILABLE + end + end +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..dd807358e --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,18 @@ +require 'simplecov' +SimpleCov.start + +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new + +require 'date' + + +# Require_relative your lib files here! +require_relative '../lib/room' +require_relative '../lib/reservation' +require_relative '../lib/admin' +require_relative '../lib/notavailable' +require_relative '../lib/block' +require_relative '../lib/date_helper'