fix bugs and add tests for PostMakeReservation handler

This commit is contained in:
vinchent 2024-07-14 11:49:42 +02:00
parent 262b48161d
commit d76070c21d
4 changed files with 226 additions and 76 deletions

View File

@ -115,19 +115,16 @@ func (m *Repository) MakeReservation(w http.ResponseWriter, r *http.Request) {
func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request) { func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request) {
reservation, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation) reservation, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
if !ok { if !ok {
helpers.ServerError(w, errors.New("connot get item from session")) m.App.Session.Put(r.Context(), "error", "can't get reservation from session")
return http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
err := r.ParseForm()
if err != nil {
helpers.ServerError(w, err)
return return
} }
reservation.FirstName = r.Form.Get("first_name") if err := r.ParseForm(); err != nil {
reservation.LastName = r.Form.Get("last_name") m.App.Session.Put(r.Context(), "error", "can't parse form")
reservation.Email = r.Form.Get("email") http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
reservation.Phone = r.Form.Get("phone") return
}
form := forms.New(r.PostForm) form := forms.New(r.PostForm)
@ -135,6 +132,13 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
form.MinLength("first_name", 2) form.MinLength("first_name", 2)
form.IsEmail("email") form.IsEmail("email")
reservation.FirstName = form.Get("first_name")
reservation.LastName = form.Get("last_name")
reservation.Email = form.Get("email")
reservation.Phone = form.Get("phone")
// TODO: Should I check the validity of reservation.StartDate / EndDate?
if !form.Valid() { if !form.Valid() {
data := make(map[string]interface{}) data := make(map[string]interface{})
data["reservation"] = reservation data["reservation"] = reservation
@ -148,7 +152,8 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
newReservationID, err := m.DB.InsertReservation(reservation) newReservationID, err := m.DB.InsertReservation(reservation)
if err != nil { 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 return
} }
@ -165,7 +170,8 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
err = m.DB.InsertRoomRestriction(restriction) err = m.DB.InsertRoomRestriction(restriction)
if err != nil { if err != nil {
helpers.ServerError(w, err) m.App.Session.Put(r.Context(), "error", "can't insert room restriction into database")
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return return
} }
@ -208,6 +214,11 @@ func (m *Repository) Availability(w http.ResponseWriter, r *http.Request) {
// PostAvailability is the search for availability page handler // PostAvailability is the search for availability page handler
func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) { func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); 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") start := r.Form.Get("start")
end := r.Form.Get("end") end := r.Form.Get("end")
@ -265,6 +276,11 @@ type jsonResponse struct {
// AvailabilityJSON is the search for availability page handler // AvailabilityJSON is the search for availability page handler
func (m *Repository) AvailabilityJSON(w http.ResponseWriter, r *http.Request) { func (m *Repository) AvailabilityJSON(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
m.App.Session.Put(r.Context(), "error", "can't parse form")
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
sd := r.Form.Get("start") sd := r.Form.Get("start")
ed := r.Form.Get("end") ed := r.Form.Get("end")

View File

@ -2,11 +2,14 @@ package handlers
import ( import (
"context" "context"
"fmt"
"go-udemy-web-1/internal/models" "go-udemy-web-1/internal/models"
"log" "log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"time"
) )
type postData struct { type postData struct {
@ -60,59 +63,196 @@ func TestHandlers(t *testing.T) {
} }
} }
func TestRepository_Reservation(t *testing.T) { // {{{ Make Reservation Tests
reservation := models.Reservation{
RoomID: 1, var makeReservationTests = []struct {
Room: models.Room{ name string
ID: 1, roomID int
RoomName: "General's Quarters", expectedStatusCode int
}, }{
{"ok", 1, http.StatusOK},
{"no session", 0, http.StatusTemporaryRedirect},
{"non-existant room", 100, http.StatusTemporaryRedirect},
} }
func TestRepository_MakeReservation(t *testing.T) {
for _, test := range makeReservationTests {
reservation := models.Reservation{
RoomID: test.roomID,
Room: models.Room{
ID: test.roomID,
RoomName: "Room name",
},
}
req, _ := http.NewRequest("GET", "/make-reservation", nil) req, _ := http.NewRequest("GET", "/make-reservation", nil)
ctx := getCtx(req) ctx := getCtx(req)
req = req.WithContext(ctx) req = req.WithContext(ctx)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
if test.roomID == 0 {
session.Put(ctx, "reservation", nil)
} else {
session.Put(ctx, "reservation", reservation) session.Put(ctx, "reservation", reservation)
}
handler := http.HandlerFunc(Repo.MakeReservation) handler := http.HandlerFunc(Repo.MakeReservation)
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK { if rr.Code != test.expectedStatusCode {
t.Errorf("Reservation handler returned response code: got %d, wanted %d", t.Errorf("for %s, reservation handler returned response code: got %d, wanted %d",
rr.Code, http.StatusOK) test.name, rr.Code, http.StatusOK)
}
}
} }
// test case where reservation is not in session (reset everything) // }}}
req, _ = http.NewRequest("GET", "/make-reservation", nil) // {{{ PostMakeReservation tests
ctx = getCtx(req)
req = req.WithContext(ctx)
rr = httptest.NewRecorder()
handler.ServeHTTP(rr, req) var postMakeReservationTests = []struct {
if rr.Code != http.StatusTemporaryRedirect { name string
t.Errorf("Reservation handler returned response code: got %d, wanted %d", reservationInfo []string
rr.Code, http.StatusTemporaryRedirect) roomID int
expectedStatusCode int
}{
{
"ok",
[]string{
"first_name=John",
"last_name=Smith",
"email=john@smith.com",
"phone=1234",
"room_id=1",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
1, http.StatusSeeOther,
},
{
"no_session",
[]string{
"first_name=John",
"last_name=Smith",
"email=john@smith.com",
"phone=1234",
"room_id=0",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
0, http.StatusTemporaryRedirect,
},
{
"no_post_data",
[]string{},
0, http.StatusTemporaryRedirect,
},
{
"missing first name",
[]string{
"last_name=Smith",
"email=john@smith.com",
"phone=1234",
"room_id=1",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
1, http.StatusOK,
},
{
"wrong first name",
[]string{
"first_name=J",
"last_name=Smith",
"email=john@smith.com",
"phone=1234",
"room_id=1",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
1, http.StatusOK,
},
{
"wrong email",
[]string{
"first_name=John",
"last_name=Smith",
"email=john@smith",
"phone=1234",
"room_id=1",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
1, http.StatusOK,
},
{
"insert reservation error",
[]string{
"first_name=John",
"last_name=Smith",
"email=john@smith.com",
"phone=1234",
"room_id=2",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
2, http.StatusTemporaryRedirect,
},
{
"insert room restriction error",
[]string{
"first_name=John",
"last_name=Smith",
"email=john@smith.com",
"phone=1234",
"room_id=100",
"start_date=2050-01-01",
"end_date=2050-01-02",
},
100, http.StatusTemporaryRedirect,
},
} }
// test with non-existant room func TestRepository_PostMakeReservation(t *testing.T) {
req, _ = http.NewRequest("GET", "/make-reservation", nil) for _, test := range postMakeReservationTests {
ctx = getCtx(req) var reqBody string
if len(test.reservationInfo) > 0 {
reqBody = test.reservationInfo[0]
for _, element := range test.reservationInfo[1:] {
reqBody = fmt.Sprintf("%s&%s", reqBody, element)
}
}
layout := "2006-01-02"
sd, _ := time.Parse(layout, "2050-01-01")
ed, _ := time.Parse(layout, "2050-01-02")
reservation := models.Reservation{
RoomID: test.roomID,
StartDate: sd,
EndDate: ed,
}
req, _ := http.NewRequest("POST", "/make-reservation", strings.NewReader(reqBody))
ctx := getCtx(req)
req = req.WithContext(ctx) req = req.WithContext(ctx)
rr = httptest.NewRecorder() req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
reservation.RoomID = 100
rr := httptest.NewRecorder()
if test.roomID == 0 {
session.Put(ctx, "reservation", nil)
} else {
session.Put(ctx, "reservation", reservation) session.Put(ctx, "reservation", reservation)
}
handler := http.HandlerFunc(Repo.PostMakeReservation)
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
if rr.Code != http.StatusTemporaryRedirect {
t.Errorf("Reservation handler returned response code: got %d, wanted %d", if rr.Code != test.expectedStatusCode {
rr.Code, http.StatusTemporaryRedirect) fmt.Printf("for %s, reservation handler returned response code: got %d, wanted %d",
test.name, rr.Code, test.expectedStatusCode)
} }
} }
}
// }}}
// {{{ Test Helpers
func getCtx(req *http.Request) context.Context { func getCtx(req *http.Request) context.Context {
ctx, err := session.Load(req.Context(), req.Header.Get("X-Session")) ctx, err := session.Load(req.Context(), req.Header.Get("X-Session"))
@ -122,3 +262,5 @@ func getCtx(req *http.Request) context.Context {
return ctx return ctx
} }
// }}}

View File

@ -17,7 +17,6 @@ import (
"github.com/alexedwards/scs/v2" "github.com/alexedwards/scs/v2"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/justinas/nosurf"
) )
var functions = template.FuncMap{} var functions = template.FuncMap{}
@ -65,7 +64,7 @@ func getRoutes() http.Handler {
mux := chi.NewMux() mux := chi.NewMux()
mux.Use(middleware.Recoverer) mux.Use(middleware.Recoverer)
// mux.Use(NoSurf) // No need to test mux.Use(WriteToConsole)
mux.Use(SessionLoad) mux.Use(SessionLoad)
mux.Get("/", Repo.Home) mux.Get("/", Repo.Home)
@ -95,20 +94,6 @@ func WriteToConsole(next http.Handler) http.Handler {
}) })
} }
// NoSurf adds CSRF protection to all POST requests
func NoSurf(next http.Handler) http.Handler {
csrfHandler := nosurf.New(next)
csrfHandler.SetBaseCookie(http.Cookie{
HttpOnly: true,
Path: "/",
Secure: app.InProduction,
SameSite: http.SameSiteLaxMode,
})
return csrfHandler
}
// SessionLoad loads and saves the session on every request // SessionLoad loads and saves the session on every request
func SessionLoad(next http.Handler) http.Handler { func SessionLoad(next http.Handler) http.Handler {
return session.LoadAndSave(next) return session.LoadAndSave(next)

View File

@ -12,11 +12,18 @@ func (m *testDBRepo) AllUsers() bool {
// InsertReservation inserts a reservation into the database // InsertReservation inserts a reservation into the database
func (m *testDBRepo) InsertReservation(res models.Reservation) (int, error) { 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("deliberate error")
}
return 1, nil return 1, nil
} }
// InsertRoomRestriction inserts a room restriction into the database // InsertRoomRestriction inserts a room restriction into the database
func (m *testDBRepo) InsertRoomRestriction(r models.RoomRestriction) error { func (m *testDBRepo) InsertRoomRestriction(r models.RoomRestriction) error {
if r.RoomID == 100 {
return errors.New("deliberate error")
}
return nil return nil
} }
@ -36,7 +43,7 @@ func (m *testDBRepo) GetRoomById(id int) (models.Room, error) {
var room models.Room var room models.Room
if id > 2 { if id > 2 {
return room, errors.New("Deliberate error") return room, errors.New("deliberate error")
} }
return room, nil return room, nil
} }