From 9f256ec1fa5624ed38d7c1e645b7a146ef904fd5 Mon Sep 17 00:00:00 2001 From: Abneed Date: Sat, 16 Jul 2022 14:07:05 -0500 Subject: [PATCH] Update unit tests --- internal/handlers/handlers.go | 167 +++-- internal/handlers/handlers_test.go | 820 ++++++++++++++++++++++-- internal/handlers/setup_test.go | 11 +- internal/render/render_test.go | 8 +- internal/repository/dbrepo/dbrepo.go | 11 + internal/repository/dbrepo/test-repo.go | 105 +++ 6 files changed, 1026 insertions(+), 96 deletions(-) create mode 100644 internal/repository/dbrepo/test-repo.go diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 53017ab..cbb4656 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -2,12 +2,11 @@ package handlers import ( "encoding/json" - "errors" "net/http" "strconv" + "strings" "time" - "github.com/abneed/bookings/helpers" "github.com/abneed/bookings/internal/config" "github.com/abneed/bookings/internal/driver" "github.com/abneed/bookings/internal/forms" @@ -15,7 +14,6 @@ import ( "github.com/abneed/bookings/internal/render" "github.com/abneed/bookings/internal/repository" "github.com/abneed/bookings/internal/repository/dbrepo" - "github.com/go-chi/chi" ) // Repo the repository used by the handlers @@ -35,6 +33,14 @@ func NewRepo(a *config.AppConfig, db *driver.DB) *Repository { } } +// NewTestRepo creates a new repository +func NewTestRepo(a *config.AppConfig) *Repository { + return &Repository{ + App: a, + DB: dbrepo.NewTestRepo(a), + } +} + // NewHandlers sets the repository for the handlers func NewHandlers(r *Repository) { Repo = r @@ -56,13 +62,15 @@ func (m *Repository) About(w http.ResponseWriter, r *http.Request) { func (m *Repository) Reservation(w http.ResponseWriter, r *http.Request) { res, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation) if !ok { - helpers.ServerError(w, errors.New("cannot get reservation from session")) + m.App.Session.Put(r.Context(), "error", "can't get reservation from session") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } room, err := m.DB.GetRoomById(res.RoomID) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't find room!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } @@ -89,22 +97,49 @@ func (m *Repository) Reservation(w http.ResponseWriter, r *http.Request) { // PostReservation handles the posting of a reservation form func (m *Repository) PostReservation(w http.ResponseWriter, r *http.Request) { - reservation, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation) - if !ok { - helpers.ServerError(w, errors.New("can't get from session")) + err := r.ParseForm() + if err != nil { + m.App.Session.Put(r.Context(), "error", "can't parse form!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } - err := r.ParseForm() + sd := r.Form.Get("start_date") + ed := r.Form.Get("end_date") + + // 2020-01-01 -- 01/02 03:04:05PM `06 -0700 + layout := "2006-01-02" + + startDate, err := time.Parse(layout, sd) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse start date") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } - reservation.FirstName = r.Form.Get("first_name") - reservation.LastName = r.Form.Get("last_name") - reservation.Phone = r.Form.Get("phone") - reservation.Email = r.Form.Get("email") + endDate, err := time.Parse(layout, ed) + if err != nil { + m.App.Session.Put(r.Context(), "error", "can't parse end date") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + return + } + + roomID, err := strconv.Atoi(r.Form.Get("room_id")) + if err != nil { + m.App.Session.Put(r.Context(), "error", "invalid data!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + return + } + + reservation := models.Reservation{ + FirstName: r.Form.Get("first_name"), + LastName: r.Form.Get("last_name"), + Phone: r.Form.Get("phone"), + Email: r.Form.Get("email"), + StartDate: startDate, + EndDate: endDate, + RoomID: roomID, + } form := forms.New(r.PostForm) @@ -115,7 +150,7 @@ func (m *Repository) PostReservation(w http.ResponseWriter, r *http.Request) { if !form.Valid() { data := make(map[string]interface{}) data["reservation"] = reservation - + http.Error(w, "my own message", http.StatusSeeOther) render.Template(w, r, "make-reservation.page.html", &models.TemplateData{ Form: form, Data: data, @@ -125,12 +160,11 @@ func (m *Repository) PostReservation(w http.ResponseWriter, r *http.Request) { newReservationID, err := m.DB.InsertReservation(reservation) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't insert reservation into database!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } - m.App.Session.Put(r.Context(), "reservation", reservation) - restriction := models.RoomRestriction{ StartDate: reservation.StartDate, EndDate: reservation.EndDate, @@ -141,7 +175,8 @@ func (m *Repository) PostReservation(w http.ResponseWriter, r *http.Request) { err = m.DB.InsertRoomRestriction(restriction) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't insert room restriction!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } m.App.Session.Put(r.Context(), "reservation", reservation) @@ -166,31 +201,37 @@ func (m *Repository) Availability(w http.ResponseWriter, r *http.Request) { // PostAvailability renders the search availability page func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + m.App.Session.Put(r.Context(), "error", "can't parse form!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + return + } + start := r.Form.Get("start") end := r.Form.Get("end") layout := "2006-01-02" startDate, err := time.Parse(layout, start) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse start date!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } endDate, err := time.Parse(layout, end) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse end date!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } rooms, err := m.DB.SearchAvailabilityForAllRooms(startDate, endDate) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't get availability for rooms") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } - for _, i := range rooms { - m.App.InfoLog.Println("ROOM:", i.ID, i.RoomName) - } - if len(rooms) == 0 { // no availability m.App.Session.Put(r.Context(), "error", "No availability") @@ -223,24 +264,53 @@ type jsonResponse struct { // AvailabilityJSON handles request for availability and send JSON response func (m *Repository) AvailabilityJSON(w http.ResponseWriter, r *http.Request) { + // need to parse request body + err := r.ParseForm() + if err != nil { + // can't parse form, so return appropriate json + resp := jsonResponse{ + OK: false, + Message: "Internal server error", + } + + out, _ := json.MarshalIndent(resp, "", " ") + w.Header().Set("Content-Type", "application/json") + w.Write(out) + return + } + sd := r.Form.Get("start") ed := r.Form.Get("end") layout := "2006-01-02" startDate, err := time.Parse(layout, sd) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse start date!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } endDate, err := time.Parse(layout, ed) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse end date!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } roomID, _ := strconv.Atoi(r.Form.Get("room_id")) - available, _ := m.DB.SearchAvailabilityByDatesByRoomID(startDate, endDate, roomID) + available, err := m.DB.SearchAvailabilityByDatesByRoomID(startDate, endDate, roomID) + if err != nil { + // got a database error, so return appropriate json + resp := jsonResponse{ + OK: false, + Message: "Error connecting to database", + } + + out, _ := json.MarshalIndent(resp, "", " ") + w.Header().Set("Content-Type", "application/json") + w.Write(out) + return + } resp := jsonResponse{ OK: available, Message: "", @@ -249,11 +319,9 @@ func (m *Repository) AvailabilityJSON(w http.ResponseWriter, r *http.Request) { RoomID: strconv.Itoa(roomID), } - out, err := json.MarshalIndent(resp, "", " ") - if err != nil { - helpers.ServerError(w, err) - return - } + // I removed the error check, since we handle all aspects of + // the json right here + out, _ := json.MarshalIndent(resp, "", " ") w.Header().Set("Content-Type", "application/json") w.Write(out) @@ -268,7 +336,6 @@ func (m *Repository) Contact(w http.ResponseWriter, r *http.Request) { func (m *Repository) ReservationSummary(w http.ResponseWriter, r *http.Request) { reservation, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation) if !ok { - m.App.ErrorLog.Println("Can't get error from session") m.App.Session.Put(r.Context(), "error", "Can't get reservation from session") http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return @@ -282,7 +349,6 @@ func (m *Repository) ReservationSummary(w http.ResponseWriter, r *http.Request) sd := reservation.StartDate.Format("2006-01-02") ed := reservation.EndDate.Format("2006-01-02") stringMap := make(map[string]string) - stringMap["start_date"] = sd stringMap["end_date"] = ed @@ -294,15 +360,29 @@ func (m *Repository) ReservationSummary(w http.ResponseWriter, r *http.Request) // ChooseRoom displays list of available rooms func (m *Repository) ChooseRoom(w http.ResponseWriter, r *http.Request) { - roomID, err := strconv.Atoi(chi.URLParam(r, "id")) + // used to have next 6 lines + //roomID, err := strconv.Atoi(chi.URLParam(r, "id")) + //if err != nil { + // log.Println(err) + // m.App.Session.Put(r.Context(), "error", "missing url parameter") + // http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + // return + //} + + // changed to this, so we can test it more easily + // split the URL up by /, and grab the 3rd element + exploded := strings.Split(r.RequestURI, "/") + roomID, err := strconv.Atoi(exploded[2]) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "missing url parameter") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } res, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation) if !ok { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "Can't get reservation from session") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } @@ -322,12 +402,14 @@ func (m *Repository) BookRoom(w http.ResponseWriter, r *http.Request) { layout := "2006-01-02" startDate, err := time.Parse(layout, sd) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse start date!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } endDate, err := time.Parse(layout, ed) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "can't parse end date!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } @@ -335,7 +417,8 @@ func (m *Repository) BookRoom(w http.ResponseWriter, r *http.Request) { room, err := m.DB.GetRoomById(roomID) if err != nil { - helpers.ServerError(w, err) + m.App.Session.Put(r.Context(), "error", "Can't get room from db!") + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 7759919..19edcab 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -1,10 +1,18 @@ package handlers import ( + "context" + "encoding/json" + "fmt" + "log" "net/http" "net/http/httptest" - "net/url" + "reflect" + "strings" "testing" + + "github.com/abneed/bookings/internal/driver" + "github.com/abneed/bookings/internal/models" ) type postData struct { @@ -16,30 +24,23 @@ var theTests = []struct { name string url string method string - params []postData expectedStatusCode int }{ - {"home", "/", "GET", []postData{}, http.StatusOK}, - {"about", "/about", "GET", []postData{}, http.StatusOK}, - {"gs", "/generals-quarters", "GET", []postData{}, http.StatusOK}, - {"ms", "/majors-suite", "GET", []postData{}, http.StatusOK}, - {"sa", "/search-availability", "GET", []postData{}, http.StatusOK}, - {"contact", "/contact", "GET", []postData{}, http.StatusOK}, - {"mr", "/make-reservation", "GET", []postData{}, http.StatusOK}, - {"post-search-avail", "/search-availability", "POST", []postData{ - {key: "start", value: "2020-01-01"}, - {key: "end", value: "2020-01-02"}, - }, http.StatusOK}, - {"post-search-avail-json", "/search-availability-json", "POST", []postData{ - {key: "start", value: "2020-01-01"}, - {key: "end", value: "2020-01-02"}, - }, http.StatusOK}, - {"make reservation post", "/make-reservation", "POST", []postData{ - {key: "first_name", value: "Abneed"}, - {key: "last_name", value: "Rodriguez"}, - {key: "email", value: "me@here.com"}, - {key: "phone", value: "555-555-5555"}, - }, http.StatusOK}, + {"home", "/", "GET", http.StatusOK}, + {"about", "/about", "GET", http.StatusOK}, + {"gs", "/generals-quarters", "GET", http.StatusOK}, + {"ms", "/majors-suite", "GET", http.StatusOK}, + {"sa", "/search-availability", "GET", http.StatusOK}, + {"contact", "/contact", "GET", http.StatusOK}, + + // {"post-search-avail", "/search-availability", "POST", []postData{ + // {key: "start", value: "2020-01-01"}, + // {key: "end", value: "2020-01-02"}, + // }, http.StatusOK}, + // {"post-search-avail-json", "/search-availability-json", "POST", []postData{ + // {key: "start", value: "2020-01-01"}, + // {key: "end", value: "2020-01-02"}, + // }, http.StatusOK}, } func TestHandlers(t *testing.T) { @@ -48,30 +49,755 @@ func TestHandlers(t *testing.T) { defer ts.Close() for _, e := range theTests { - if e.method == "GET" { - resp, err := ts.Client().Get(ts.URL + e.url) - if err != nil { - t.Log(err) - t.Fatal(err) - } - - if resp.StatusCode != e.expectedStatusCode { - t.Errorf("for %s, expected %d but got %d", e.name, e.expectedStatusCode, resp.StatusCode) - } - } else { - values := url.Values{} - for _, x := range e.params { - values.Add(x.key, x.value) - } - resp, err := ts.Client().PostForm(ts.URL+e.url, values) - if err != nil { - t.Log(err) - t.Fatal(err) - } - - if resp.StatusCode != e.expectedStatusCode { - t.Errorf("for %s, expected %d but got %d", e.name, e.expectedStatusCode, resp.StatusCode) - } + resp, err := ts.Client().Get(ts.URL + e.url) + if err != nil { + t.Log(err) + t.Fatal(err) + } + + if resp.StatusCode != e.expectedStatusCode { + t.Errorf("for %s, expected %d but got %d", e.name, e.expectedStatusCode, resp.StatusCode) } } } + +func TestRepository_Reservation(t *testing.T) { + reservation := models.Reservation{ + RoomID: 1, + Room: models.Room{ + ID: 1, + RoomName: "General's Quarters", + }, + } + + req, _ := http.NewRequest("GET", "/make-reservation", nil) + ctx := getCtx(req) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + session.Put(ctx, "reservation", reservation) + + handler := http.HandlerFunc(Repo.Reservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Errorf("Reservation handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusOK) + } + + // test case where reservation is not in session (reset everything) + req, _ = http.NewRequest("GET", "/make-reservation", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + rr = httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test with non-existent room + req, _ = http.NewRequest("GET", "/make-reservation", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + rr = httptest.NewRecorder() + reservation.RoomID = 100 + session.Put(ctx, "reservation", reservation) + + handler.ServeHTTP(rr, req) + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusOK) + } +} + +func TestRepository_PostReservation(t *testing.T) { + reqBody := "start_date=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=2050-01-2") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=Abneed") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + + req, _ := http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx := getCtx(req) + req = req.WithContext(ctx) + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + + handler := http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("PostReservation handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test for missing post body + req, _ = http.NewRequest("POST", "/make-reservation", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler returned wrong response code for missing post body: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test for invalid start date + reqBody = "start_date=invalid" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=2050-01-2") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=Abneed") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + + req, _ = http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler returned wrong response code for invalid start date: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test for invalid end date + reqBody = "start_date=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=invalid") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=Abneed") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + + req, _ = http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler returned wrong response code for invalid end date: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test for invalid room id + reqBody = "start_date=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=2050-01-02") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=Abneed") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=invalid") + + req, _ = http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler returned wrong response code for invalid room id: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test for invalid data + reqBody = "start_date=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=2050-01-02") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=A") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + + req, _ = http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusSeeOther { + t.Errorf("Reservation handler returned wrong response code for invalid data: got %d, wanted %d", rr.Code, http.StatusSeeOther) + } + + // test for failure to insert reservation into database + reqBody = "start_date=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=2050-01-02") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=Abneed") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=2") + + req, _ = http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler failed when trying to fail inserting reservation: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + // test for failure to insert restriction into database + reqBody = "start_date=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end_date=2050-01-02") + reqBody = fmt.Sprintf("%s&%s", reqBody, "first_name=Abneed") + reqBody = fmt.Sprintf("%s&%s", reqBody, "last_name=Rodriguez") + reqBody = fmt.Sprintf("%s&%s", reqBody, "email=abneed.rodriguez@gmail.com") + reqBody = fmt.Sprintf("%s&%s", reqBody, "phone=+5218671487159") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1000") + + req, _ = http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody)) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.PostReservation) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Reservation handler failed when trying to fail inserting restriction: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } +} + +func TestNewRepo(t *testing.T) { + var db driver.DB + testRepo := NewRepo(&app, &db) + + if reflect.TypeOf(testRepo).String() != "*handlers.Repository" { + t.Errorf("Did not get correct type from NewRepo: got %s, wanted *Repository", reflect.TypeOf(testRepo).String()) + } +} + +func TestRepository_PostAvailability(t *testing.T) { + /***************************************** + // first case -- rooms are not available + *****************************************/ + // create our request body + reqBody := "start=2050-01-02" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2050-01-02") + + // create our request + req, _ := http.NewRequest("POST", "/search_availability", strings.NewReader(reqBody)) + + // get the context with session + ctx := getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr := httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler := http.HandlerFunc(Repo.PostAvailability) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // since we have no rooms available, we expect to get status http.StatusSeeOther + if rr.Code != http.StatusSeeOther { + t.Errorf("Post availability when no rooms available gave wrong status code: got %d, wanted %d", rr.Code, http.StatusSeeOther) + } + + /***************************************** + // second case -- rooms are available + *****************************************/ + // this time, we specify a start date before 2040-01-01, which will give us + // a non-empty slice, indicating that rooms are available + reqBody = "start=2040-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2040-01-02") + + // create our request + req, _ = http.NewRequest("POST", "/search-availability", strings.NewReader(reqBody)) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.PostAvailability) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // since we have rooms available, we expect to get status http.StatusOK + if rr.Code != http.StatusOK { + t.Errorf("Post availability when rooms are available gave wrong status code: got %d, wanted %d", rr.Code, http.StatusOK) + } + + /***************************************** + // third case -- empty post body + *****************************************/ + // create our request with a nil body, so parsing form fails + req, _ = http.NewRequest("POST", "/search-availability", nil) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.PostAvailability) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // since we have rooms available, we expect to get status http.StatusTemporaryRedirect + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Post availability with empty request body (nil) gave wrong status code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + /***************************************** + // fourth case -- start date in wrong format + *****************************************/ + // this time, we specify a start date in the wrong format + reqBody = "start=invalid" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2040-01-02") + req, _ = http.NewRequest("POST", "/search-availability", strings.NewReader(reqBody)) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.PostAvailability) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // since we have rooms available, we expect to get status http.StatusTemporaryRedirect + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Post availability with invalid start date gave wrong status code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + /***************************************** + // fifth case -- end date in wrong format + *****************************************/ + // this time, we specify a start date in the wrong format + reqBody = "start=2040-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "invalid") + req, _ = http.NewRequest("POST", "/search-availability", strings.NewReader(reqBody)) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.PostAvailability) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // since we have rooms available, we expect to get status http.StatusTemporaryRedirect + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Post availability with invalid end date gave wrong status code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + /***************************************** + // sixth case -- database query fails + *****************************************/ + // this time, we specify a start date of 2060-01-01, which will cause + // our testdb repo to return an error + reqBody = "start=2060-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2060-01-02") + req, _ = http.NewRequest("POST", "/search-availability", strings.NewReader(reqBody)) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.PostAvailability) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // since we have rooms available, we expect to get status http.StatusTemporaryRedirect + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("Post availability when database query fails gave wrong status code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } +} + +func TestRepository_AvailabilityJSON(t *testing.T) { + /***************************************** + // first case -- rooms are not available + *****************************************/ + // create our request body + reqBody := "start=2050-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2050-01-01") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + + // create request + req, _ := http.NewRequest("POST", "/search-availability-json", strings.NewReader(reqBody)) + + // get context with session + ctx := getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr := httptest.NewRecorder() + + // make handler a http.Handlerfunc + handler := http.HandlerFunc(Repo.AvailabilityJSON) + + // make request to our handler + handler.ServeHTTP(rr, req) + + // since we have no rooms available, we expect to get status http.StatusSeeOther + // this time we want to parse JSON and get the expected response + var j jsonResponse + err := json.Unmarshal(rr.Body.Bytes(), &j) + if err != nil { + t.Error("failed to parse json") + } + + // since we specified a start date > 2049-12-31, we expect no availability + if j.OK { + t.Error("Got availability when none was expected in AvailabilityJSON") + } + + /***************************************** + // second case -- rooms not available + *****************************************/ + // create our request body + reqBody = "start=2040-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2040-01-02") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + + // create our request + req, _ = http.NewRequest("POST", "/search-availability-json", strings.NewReader(reqBody)) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.AvailabilityJSON) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // this time we want to parse JSON and get the expected response + err = json.Unmarshal(rr.Body.Bytes(), &j) + if err != nil { + t.Error("failed to parse json!") + } + + // since we specified a start date < 2049-12-31, we expect availability + if !j.OK { + t.Error("Got no availability when some was expected in AvailabilityJSON") + } + + /***************************************** + // third case -- no request body + *****************************************/ + // create our request + req, _ = http.NewRequest("POST", "/search-availability-json", nil) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.AvailabilityJSON) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // this time we want to parse JSON and get the expected response + err = json.Unmarshal(rr.Body.Bytes(), &j) + if err != nil { + t.Error("failed to parse json!") + } + + // since we specified a start date < 2049-12-31, we expect availability + if j.OK || j.Message != "Internal server error" { + t.Error("Got availability when request body was empty") + } + + /***************************************** + // fourth case -- database error + *****************************************/ + // create our request body + reqBody = "start=2060-01-01" + reqBody = fmt.Sprintf("%s&%s", reqBody, "end=2060-01-02") + reqBody = fmt.Sprintf("%s&%s", reqBody, "room_id=1") + req, _ = http.NewRequest("POST", "/search-availability-json", strings.NewReader(reqBody)) + + // get the context with session + ctx = getCtx(req) + req = req.WithContext(ctx) + + // set the request header + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // create our response recorder, which satisfies the requirements + // for http.ResponseWriter + rr = httptest.NewRecorder() + + // make our handler a http.HandlerFunc + handler = http.HandlerFunc(Repo.AvailabilityJSON) + + // make the request to our handler + handler.ServeHTTP(rr, req) + + // this time we want to parse JSON and get the expected response + err = json.Unmarshal(rr.Body.Bytes(), &j) + if err != nil { + t.Error("failed to parse json!") + } + + // since we specified a start date < 2049-12-31, we expect availability + if j.OK || j.Message != "Error querying database" { + t.Error("Got availability when simulating database error") + } +} + +func TestRepository_ReservationSummary(t *testing.T) { + /***************************************** + // first case -- reservation in session + *****************************************/ + reservation := models.Reservation{ + RoomID: 1, + Room: models.Room{ + ID: 1, + RoomName: "General's Quarters", + }, + } + + req, _ := http.NewRequest("GET", "/reservation-summary", nil) + ctx := getCtx(req) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + session.Put(ctx, "reservation", reservation) + + handler := http.HandlerFunc(Repo.ReservationSummary) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Errorf("ReservationSummary handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusOK) + } + + /***************************************** + // second case -- reservation not in session + *****************************************/ + req, _ = http.NewRequest("GET", "/reservation-summary", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.ReservationSummary) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("ReservationSummary handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusOK) + } +} + +func TestRepository_ChooseRoom(t *testing.T) { + /***************************************** + // first case -- reservation in session + *****************************************/ + reservation := models.Reservation{ + RoomID: 1, + Room: models.Room{ + ID: 1, + RoomName: "General's Quarters", + }, + } + + req, _ := http.NewRequest("GET", "/choose-room/1", nil) + ctx := getCtx(req) + req = req.WithContext(ctx) + // set the RequestURI on the request so that we can grab the ID + // from the URL + req.RequestURI = "/choose-room/1" + + rr := httptest.NewRecorder() + session.Put(ctx, "reservation", reservation) + + handler := http.HandlerFunc(Repo.ChooseRoom) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusSeeOther { + t.Errorf("ChooseRoom handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + ///***************************************** + //// second case -- reservation not in session + //*****************************************/ + req, _ = http.NewRequest("GET", "/choose-room/1", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.RequestURI = "/choose-room/1" + + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.ChooseRoom) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("ChooseRoom handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } + + ///***************************************** + //// third case -- missing url parameter, or malformed parameter + //*****************************************/ + req, _ = http.NewRequest("GET", "/choose-room/fish", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + req.RequestURI = "/choose-room/fish" + + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.ChooseRoom) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("ChooseRoom handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } +} + +func TestRepository_BookRoom(t *testing.T) { + /***************************************** + // first case -- database works + *****************************************/ + reservation := models.Reservation{ + RoomID: 1, + Room: models.Room{ + ID: 1, + RoomName: "General's Quarters", + }, + } + + req, _ := http.NewRequest("GET", "/book-room?s=2050-01-01&e=2050-01-02&id=1", nil) + ctx := getCtx(req) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + session.Put(ctx, "reservation", reservation) + + handler := http.HandlerFunc(Repo.BookRoom) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusSeeOther { + t.Errorf("BookRoom handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusSeeOther) + } + + /***************************************** + // second case -- database failed + *****************************************/ + req, _ = http.NewRequest("GET", "/book-room?s=2040-01-01&e=2040-01-02&id=4", nil) + ctx = getCtx(req) + req = req.WithContext(ctx) + + rr = httptest.NewRecorder() + + handler = http.HandlerFunc(Repo.BookRoom) + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusTemporaryRedirect { + t.Errorf("BookRoom handler returned wrong response code: got %d, wanted %d", rr.Code, http.StatusTemporaryRedirect) + } +} + +func getCtx(req *http.Request) context.Context { + ctx, err := session.Load(req.Context(), req.Header.Get("X-Session")) + if err != nil { + log.Println(err) + } + return ctx +} diff --git a/internal/handlers/setup_test.go b/internal/handlers/setup_test.go index f8bfde4..fe87d18 100644 --- a/internal/handlers/setup_test.go +++ b/internal/handlers/setup_test.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "testing" "time" "github.com/abneed/bookings/internal/config" @@ -24,7 +25,7 @@ var session *scs.SessionManager var pathToTemplates = "./../../templates" var functions = template.FuncMap{} -func getRoutes() http.Handler { +func TestMain(m *testing.M) { gob.Register(models.Reservation{}) // change this to true when in production @@ -33,7 +34,7 @@ func getRoutes() http.Handler { infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime) app.InfoLog = infoLog - errorLog := log.New(os.Stdout, "ERROR\t", log.Ldate|log.Lshortfile) + errorLog := log.New(os.Stdout, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) app.ErrorLog = errorLog session = scs.New() @@ -52,10 +53,14 @@ func getRoutes() http.Handler { app.TemplateCache = tc app.UseCache = true - repo := NewRepo(&app) + repo := NewTestRepo(&app) NewHandlers(repo) render.NewRenderer(&app) + os.Exit(m.Run()) +} + +func getRoutes() http.Handler { mux := chi.NewRouter() mux.Use(middleware.Recoverer) diff --git a/internal/render/render_test.go b/internal/render/render_test.go index 9df02b3..3c131e2 100644 --- a/internal/render/render_test.go +++ b/internal/render/render_test.go @@ -19,7 +19,7 @@ func TestAddDefaultData(t *testing.T) { result := AddDefaultData(&td, r) - if result == nil { + if result.Flash != "123" { t.Error("flash value of 123 not found in session") } } @@ -40,12 +40,12 @@ func TestRenderTemplate(t *testing.T) { var ww myWriter - err = RenderTemplate(&ww, r, "home.page.html", &models.TemplateData{}) + err = Template(&ww, r, "home.page.html", &models.TemplateData{}) if err != nil { t.Error("error writing template to browser") } - err = RenderTemplate(&ww, r, "non-existent.page.html", &models.TemplateData{}) + err = Template(&ww, r, "non-existent.page.html", &models.TemplateData{}) if err == nil { t.Error("rendered template that does not exist") } @@ -65,7 +65,7 @@ func getSession() (*http.Request, error) { } func TestNewTemplates(t *testing.T) { - NewTemplates(app) + NewRenderer(app) } func TestCreateTemplateCache(t *testing.T) { diff --git a/internal/repository/dbrepo/dbrepo.go b/internal/repository/dbrepo/dbrepo.go index f75b437..4ecdfda 100644 --- a/internal/repository/dbrepo/dbrepo.go +++ b/internal/repository/dbrepo/dbrepo.go @@ -12,9 +12,20 @@ type postgresDBRepo struct { DB *sql.DB } +type testDBRepo struct { + App *config.AppConfig + DB *sql.DB +} + func NewPostgresRepo(conn *sql.DB, a *config.AppConfig) repository.DatabaseRepo { return &postgresDBRepo{ App: a, DB: conn, } } + +func NewTestRepo(a *config.AppConfig) repository.DatabaseRepo { + return &testDBRepo{ + App: a, + } +} diff --git a/internal/repository/dbrepo/test-repo.go b/internal/repository/dbrepo/test-repo.go new file mode 100644 index 0000000..3a62805 --- /dev/null +++ b/internal/repository/dbrepo/test-repo.go @@ -0,0 +1,105 @@ +package dbrepo + +import ( + "errors" + "log" + "time" + + "github.com/abneed/bookings/internal/models" +) + +func (m *testDBRepo) AllUsers() bool { + return true +} + +// InsertReservation inserts a reservation into the database +func (m *testDBRepo) InsertReservation(res models.Reservation) (int, error) { + // if the room id is 2, then fail; otherwise, pass + if res.RoomID == 2 { + return 0, errors.New("some error") + } + return 1, nil +} + +// InsertRoomRestriction inserts a room restriction into the database +func (m *testDBRepo) InsertRoomRestriction(r models.RoomRestriction) error { + if r.RoomID == 1000 { + return errors.New("some error") + } + return nil +} + +// SearchAvailabilityByDatesByRoomID returns true if availability exists for roomID, and false if no availability exists +func (m *testDBRepo) SearchAvailabilityByDatesByRoomID(start, end time.Time, roomID int) (bool, error) { + // set up as a test time + layout := "2006-01-02" + str := "2049-12-31" + t, err := time.Parse(layout, str) + if err != nil { + log.Println(err) + } + + // this is our test to fail the query -- specify 2060-01-01 as start + testDateToFail, err := time.Parse(layout, "2060-01-01") + if err != nil { + log.Println(err) + } + + if start == testDateToFail { + return false, errors.New("some error") + } + + // if start date is after 2049-12-31, then return false, + // indicating no availability; + if start.After(t) { + return false, nil + } + + // otherwise, we have availability + return true, nil +} + +// SearchAvailabilityForAllRooms returns a slice of available rooms, if any, for given date range +func (m *testDBRepo) SearchAvailabilityForAllRooms(start, end time.Time) ([]models.Room, error) { + var rooms []models.Room + + // if start date is after 2049-12-31, then return empty slice, + // indicating no rooms are available; + layout := "2006-01-02" + str := "2049-12-31" + t, err := time.Parse(layout, str) + if err != nil { + log.Println(err) + } + + testDateToFail, err := time.Parse(layout, "2060-01-01") + if err != nil { + log.Println(err) + } + + if start == testDateToFail { + return rooms, errors.New("some error") + } + + if start.After(t) { + return rooms, nil + } + + // otherwise, put an entry into the slice, indicating that some room is + // available for search dates + room := models.Room{ + ID: 1, + } + rooms = append(rooms, room) + + return rooms, nil +} + +// GetRoomById gets a room by id +func (m *testDBRepo) GetRoomById(id int) (models.Room, error) { + var room models.Room + if id > 2 { + return room, errors.New("some error") + } + return room, nil +}