diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 809125482..2f23a43c9 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -5,9 +5,9 @@ Congratulations! You're submitting your assignment. Please reflect on the assign ## Reflection Question | Answer :------------- | :------------- -What was a design challenge that you encountered on this project? | -What was a design decision you made that changed over time over the project? | -What was a concept you gained clarity on, or a learning that you'd like to share? | -What is an example of a _nominal test_ that you wrote for this assignment? What makes it a nominal case? | -What is an example of an _edge case test_ that you wrote for this assignment? What makes it an edge case? | -How do you feel you did in writing pseudocode first, then writing the tests and then the code? | \ No newline at end of file +What was a design challenge that you encountered on this project? | Try to think of each class and have each one follow the principle of single responsibility. It was very difficult to think if it was too big, or if I was giving responsibilities that were not of the class or the method. +What was a design decision you made that changed over time over the project? | I thought of creating a class that would be called client but then I didn't use it. Also at the beginning I didn't have a room class and then I decided to include it. +What was a concept you gained clarity on, or a learning that you'd like to share? | Interact with classes and understand what an instance looks like. +What is an example of a _nominal test_ that you wrote for this assignment? What makes it a nominal case? | Test out the list of rooms. It is nominal because It was the basic-minimun thing my hotel structure should show. +What is an example of an _edge case test_ that you wrote for this assignment? What makes it an edge case? | Test out all the possible overlaps. Especially to test when an end_date reservation was the same as the start_date of a new reservation. +How do you feel you did in writing pseudocode first, then writing the tests and then the code? | It was easy at the beginning, drawing the classes and their responsibility, was pretty useful. For the last methods to write the tests at first it became complicated, to think about what results I wanted and how to write a real test, that really test what I want or what I expect. diff --git a/.gitignore b/.gitignore index 5e1422c9c..74ba0d4bb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ build-iPhoneSimulator/ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc +.vscode \ No newline at end of file diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..eee174b54 --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,47 @@ +require 'date' + +module Hotel + class DateRange + attr_accessor :start_date, :end_date + + def initialize(start_date, end_date) + self.class.validate_date?(start_date,end_date) + @start_date = start_date + @end_date = end_date + end + + # An input range given will not be part of any other date_range + # It returns true if there is an overlap or false if there is not. + # input_range = Hotel::DataRange instance. + def overlap?(input_range) + a = self.start_date >= input_range.end_date + b = self.end_date <= input_range.start_date + return a || b ? false : true + end + + def nights + nights_number = (end_date - start_date).to_i + return nights_number + end + + private + # validate_date? validates that start and end date have valid format. + # start date cannot be after the end date + # Exception raised when an invalid date range is provided + def self.validate_date?(start_date, end_date) + + date_format = '%Y-%m-%d' + if !DateTime.strptime(start_date.to_s, date_format) || !DateTime.strptime(end_date.to_s, date_format) + raise ArgumentError + end + + if start_date > end_date + raise ArgumentError.new("The end time is before the start time.") + end + + if start_date - end_date == 0 + raise ArgumentError.new("Start date and end date are the same.") + end + end + end +end diff --git a/lib/front_desk.rb b/lib/front_desk.rb new file mode 100644 index 000000000..20311c992 --- /dev/null +++ b/lib/front_desk.rb @@ -0,0 +1,90 @@ + +module Hotel + class FrontDesk + attr_reader :rooms, :date_range, :reservations + + def initialize + @rooms = generate_rooms + @reservations = [] + end + + def generate_rooms + hotel_rooms = Array.new + (1..20).each do |room_id| + hotel_rooms << Room.new(room_id) + end + return hotel_rooms + end + + def room_list + return @rooms + end + + def total_reservations + return @reservations + end + + # list of rooms that are not reserved for a given date range, + # so that I can see all available rooms for that day. + # It retuns an arrary with rooms with not reservations with a given date. + def get_available_rooms(date_range) + unavailable_rooms = [] + # Send the room to unavailable rooms if room has a reservation for a specific data_range. + @reservations.each do |reservation| + if reservation.daterange.overlap?(date_range) == true + unavailable_rooms << reservation.room + end + end + # Array substrating total_rooms minus unavailable rooms. + return @rooms - unavailable_rooms + end + + # The list of reservations for a specified room and a given date range. + # room = room_i d. + # date_range = data_range instance. + # It returns an array of reservation. + def reservations_by_room_date (room_number, date_range) + # reservations_by_room contains all the reservations match + # based on the room and a given date range. + reservations_by_room = @reservations.select do |reservation| + reservation.room.room_id == room_number && + reservation.daterange.overlap?(date_range) + end + return reservations_by_room + end + + # It returns an array with the list of reservations for a specific date. + # date = date instance. + def reservations_by_date(date) + total_by_date = @reservations.select do |reservation| + reservation.daterange.start_date == date + end + return total_by_date + end + + # Get the total cost for a given reservation + # reservation_number = reservartion_id + # It returns nil if reservation id not found. + def cost_by_reservation(reservation_number) + @reservations.each do |reservation| + if reservation.id == reservation_number + return reservation.total_cost + end + end + return nil + end + + # 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 + # an exception raised if I try to reserve a room during a date range when all rooms are reserved. + def make_resevation(date_range) + available_rooms = get_available_rooms(date_range) + if available_rooms.length < 1 + raise ArgumentError + end + new_reservation = Hotel::Reservation.new(date_range,available_rooms[0]) + @reservations << 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..d4f289ba7 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,25 @@ + +require 'securerandom' + +module Hotel + + class Reservation + + attr_reader :daterange, :room, :id + + def initialize(daterange, room) + @daterange = daterange + @room = room + @id = generate_id + end + + # It retuns a secureRandom ID per each reservation made. + def generate_id + return SecureRandom.hex(5) + end + + def total_cost + return daterange.nights * @room.cost + end + end +end \ No newline at end of file diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..3a3215543 --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,12 @@ + +module Hotel + class Room + attr_reader :room_id, :capacity, :cost + + def initialize(room_id, capacity: 2, cost: 200) + @room_id = room_id + @capacity = capacity + @cost = cost + end + end +end diff --git a/test/data_range_test.rb b/test/data_range_test.rb new file mode 100644 index 000000000..4834dc34c --- /dev/null +++ b/test/data_range_test.rb @@ -0,0 +1,122 @@ +require_relative "test_helper" +require 'date' + +# The start day cannnot be in the past +describe Hotel::DateRange do + + + describe "Initilize" do + it "Can be initialized with two dates" do + + start_date = Date.new(2017, 01, 01) + end_date = start_date + 3 + + @range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.start_date).must_equal start_date + expect(@range.end_date).must_equal end_date + end + + it "is an an error for negative-lenght ranges" do + start_date = Date.new(2017, 01, 01) + end_date = Date.new(2015, 01, 01) + + expect{ + Hotel::DateRange.new(start_date, end_date) + }.must_raise ArgumentError + + end + + it "is an error to create a 0-length range" do + start_date = Date.new(2017, 01, 01) + end_date = Date.new(2017, 01, 01) + + expect{ + Hotel::DateRange.new(start_date, end_date) + }.must_raise ArgumentError + + end + + it "raises an error for incorrect dates format" do + start_date = "" + end_date = "" + + expect { + Hotel::DateRange.new(start_date, end_date) + }.must_raise ArgumentError + + end + end + + describe "overlap?" do + before do + start_date = Date.new(2017, 01, 01) + end_date = start_date + 3 + @range = Hotel::DateRange.new(start_date, end_date) + end + + it "returns true for the same range" do + start_date = @range.start_date + end_date = @range.end_date + test_range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.overlap?(test_range)).must_equal true + end + + + it "returns true for a range that overlaps in front." do + start_date = Date.new(2017, 01, 02) + end_date = start_date + 4 + test_range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.overlap?(test_range)).must_equal true + end + + + it "returns true for a range that overlaps in the back." do + start_date = Date.new(2016, 12, 30) + end_date = start_date + 3 + test_range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.overlap?(test_range)).must_equal true + end + + it "returns false for a range ending on the start_date date." do + start_date = Date.new(2016, 12, 30) + end_date = Date.new(2017, 01, 01) + test_range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.overlap?(test_range)).must_equal false + end + + it "returns false for a range starting on the end_date date." do + start_date = Date.new(2017, 01, 04) + end_date = start_date + 7 + test_range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.overlap?(test_range)).must_equal false + end + + it "returns true for a contained range." do + start_date = Date.new(2017, 01, 02) + end_date = start_date + 1 + test_range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.overlap?(test_range)).must_equal true + end + end + + describe "nights" do + it "returns the correct number of nights" do + start_date = Date.new(2017, 01, 01) + end_date = Date.new(2017, 01, 02) + + @range = Hotel::DateRange.new(start_date, end_date) + + expect(@range.nights).must_be_kind_of Integer + expect(@range.nights).must_equal 1 + + 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..22cbaa2af --- /dev/null +++ b/test/front_desk_test.rb @@ -0,0 +1,118 @@ +require_relative 'test_helper' + +describe Hotel::FrontDesk do + # Intance of front_desk and date_range created before. Use them later to test. + before do + @front_desk = Hotel::FrontDesk.new + @date = Date.new(2020,05,21) + start_date = @date + end_date = start_date + 2 + @date_range = Hotel::DateRange.new(start_date,end_date) + end + + describe "Wave number 1" do + describe "room_list" do + it "Returns a list of rooms" do + rooms = @front_desk.room_list + expect(rooms).must_be_kind_of Array + end + + it "raises an error if the array of rooms it is empty" do + rooms = @front_desk.room_list.length + expect(rooms).must_be :>, 1 + end + + it "raises an error if there are not available rooms" do + # 20 rooms created to test the case with not available dates. + 20.times do + new_reservation = @front_desk.make_resevation(@date_range) + end + expect{ @front_desk.make_resevation(@date_range) }.must_raise ArgumentError + end + end + + describe "make_resevation" do + it "Returns a new resevation" do + new_reservation = @front_desk.make_resevation(@date_range) + expect(new_reservation).must_be_kind_of Hotel::Reservation + end + end + + describe "Reservations" do + # New reservations created before to use in all cases of reservations. + before do + @new_reservation = @front_desk.make_resevation(@date_range) + end + + it "returns an array of reservations" do + reservations = @front_desk.total_reservations + # Every reservation within the array should be a instance of reservation. + reservations.each do |reservation| + reservation.must_be_kind_of Hotel::Reservation + end + expect(reservations).must_be_kind_of Array + end + + it "returns an a array of reservations by date" do + date = Date.new(2020,05,21) + total_by_date = @front_desk.reservations_by_date(date) + expect(total_by_date).must_be_kind_of Array + end + + it "returns the total cost for a given reservation" do + reserv_id = @new_reservation.id + cost = @front_desk.cost_by_reservation(reserv_id) + expect(cost).must_equal 400 + end + + it "returns nil if reservation id not found" do + reserv_id = "fake_id" + cost = @front_desk.cost_by_reservation(reserv_id) + expect(cost).must_equal nil + end + end + + describe "List of reservations for a specified room and a given date range" do + it "returns a list of reservations for a specified room and a given date range" do + start_date = Date.new(2016,2,01) + end_date = Date.new(2016,2,03) + date_range = Hotel::DateRange.new(start_date,end_date) + reservation1 = @front_desk.make_resevation(date_range) + + start_date = Date.new(2016,2,04) + end_date = Date.new(2016,2,06) + date_range = Hotel::DateRange.new(start_date,end_date) + reservation2 = @front_desk.make_resevation(date_range) + + start_date = Date.new(2016,2,07) + end_date = Date.new(2016,2,9) + date_range = Hotel::DateRange.new(start_date,end_date) + reservation3 = @front_desk.make_resevation(date_range) + + start_date = Date.new(2016,2,10) + end_date = Date.new(2016,2,15) + date_range = Hotel::DateRange.new(start_date,end_date) + reservation4 = @front_desk.make_resevation(date_range) + + start_date = Date.new(2016,2,16) + end_date = Date.new(2016,2,18) + date_range = Hotel::DateRange.new(start_date,end_date) + reservation5 = @front_desk.make_resevation(date_range) + + start_date = Date.new(2016,2,05) + end_date = Date.new(2016,2,13) + + date_range2 = Hotel::DateRange.new(start_date,end_date) + + search = @front_desk.reservations_by_room_date(1, date_range2) + reservations = search.length + # It returns an array with 3 Reservation instances. + expect(search).must_be_kind_of Array + expect(reservations).must_equal 3 + expect(reservation2).must_be_same_as search[0] + expect(reservation3).must_be_same_as search[1] + expect(reservation4).must_be_same_as search[2] + end + 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..57509866a --- /dev/null +++ b/test/reservation_test.rb @@ -0,0 +1,19 @@ +require_relative 'test_helper' + +describe Hotel::Reservation do + + before do + @date = Date.new(2020,05,01) + start_date = @date + end_date = start_date + 2 + @datarange = Hotel::DateRange.new(start_date,end_date) + @room = Hotel::Room.new(1) + @reservation = Hotel::Reservation.new(@datarange, @room) + end + + it "Returns the total_cost of the reservation" do + cost = @reservation.total_cost + expect(cost).must_be_close_to (200 * 2), 0.01 + end + +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..61d8d640e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,8 @@ # Add simplecov + +require 'simplecov' +SimpleCov.start + require "minitest" require "minitest/autorun" require "minitest/reporters" @@ -6,3 +10,8 @@ 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' +require_relative '../lib/room' \ No newline at end of file