xtablo-source/deprecated/router_test.go
Arthur Belleville 5d0c201e86
Some checks failed
backend-ci / Backend tests (pull_request) Failing after 53s
backend-ci / Backend tests (push) Failing after 1s
Some work
2026-05-23 17:26:01 +02:00

875 lines
26 KiB
Go

package main
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"xtablo-backend/internal/web/handlers"
)
func TestRootRedirectsToLoginWhenUnauthenticated(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusSeeOther {
t.Fatalf("expected status 303, got %d", rec.Code)
}
if location := rec.Header().Get("Location"); location != "/login" {
t.Fatalf("expected redirect to /login, got %q", location)
}
}
func TestUnknownRouteRedirectsToLoginWhenUnauthenticated(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/missing", nil)
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusSeeOther {
t.Fatalf("expected status 303, got %d", rec.Code)
}
if location := rec.Header().Get("Location"); location != "/login" {
t.Fatalf("expected redirect to /login, got %q", location)
}
}
func TestLoginPageRenders(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/login", nil)
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
"Se connecter à Xtablo",
`hx-post="/login"`,
"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-beta2/dist/htmx.min.js",
`href="/static/tailwind.css"`,
`href="/pwa-icons/favicon-32x32.png"`,
`href="/pwa-icons/favicon-16x16.png"`,
`href="/pwa-icons/apple-touch-icon-180x180.png"`,
`href="/manifest.webmanifest"`,
`src="/logo_dark.png"`,
`src="/logo_white.png"`,
`data-testid="auth-card-shell"`,
"Découvrez la nouvelle expérience de connexion",
"Mot de passe oublié ?",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected body to contain %q", want)
}
}
}
func TestTailwindStylesheetIsServed(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/static/tailwind.css", nil)
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
".text-2xl",
".grid-cols-1",
".whitespace-nowrap",
".justify-end",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected tailwind.css to contain %q", want)
}
}
}
func TestSignupPageRenders(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/signup", nil)
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
"S'inscrire à Xtablo",
`hx-post="/signup"`,
"Vous avez déjà un compte ?",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected body to contain %q", want)
}
}
}
func TestBrandingAssetsAreServed(t *testing.T) {
testCases := []string{
"/logo_dark.png",
"/logo_white.png",
"/pwa-icons/favicon-32x32.png",
"/pwa-icons/favicon-16x16.png",
"/pwa-icons/apple-touch-icon-180x180.png",
"/manifest.webmanifest",
}
router := newTestRouter()
for _, path := range testCases {
req := httptest.NewRequest(http.MethodGet, path, nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected %s to return 200, got %d", path, rec.Code)
}
}
}
func TestLoginReturnsValidationError(t *testing.T) {
form := url.Values{}
form.Set("email", "")
form.Set("password", "")
req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusUnprocessableEntity {
t.Fatalf("expected status 422, got %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "Veuillez renseigner votre email et votre mot de passe") {
t.Fatalf("expected validation error fragment, got %q", rec.Body.String())
}
}
func TestLoginCreatesSessionAndRedirects(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
if redirect := rec.Header().Get("HX-Redirect"); redirect != "/" {
t.Fatalf("expected HX-Redirect to /, got %q", redirect)
}
sessionCookie := findCookie(rec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
homeReq := httptest.NewRequest(http.MethodGet, "/", nil)
homeReq.AddCookie(sessionCookie)
homeRec := httptest.NewRecorder()
router.ServeHTTP(homeRec, homeReq)
if homeRec.Code != http.StatusOK {
t.Fatalf("expected authenticated root status 200, got %d", homeRec.Code)
}
for _, want := range []string{
"Bonjour,",
"Aperçu",
"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-beta2/dist/htmx.min.js",
`hx-get="/tasks"`,
`hx-target="#app-main-content"`,
`hx-swap="outerHTML"`,
`hx-push-url="true"`,
"Tâches",
"Projets",
"Planning",
"Discussions",
"Fichiers",
"Feedback",
"Arctic Matrix",
"Créer un projet",
"Créer une tâche",
"Inviter l'équipe",
"Envoyer un message",
"Mes Projets",
"Mes Tâches",
`action="/logout"`,
} {
if !strings.Contains(homeRec.Body.String(), want) {
t.Fatalf("expected authenticated home page to contain %q, got %q", want, homeRec.Body.String())
}
}
}
func TestTasksPageRendersFullDashboardPage(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/tasks", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
`class="sidebar-nav-shell"`,
`id="app-main-content"`,
`data-current-view="kanban"`,
"Mes Tâches",
"Nouvelle tâche",
"Tableau",
"Filtrer",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected tasks page to contain %q", want)
}
}
}
func TestHomePageProjectsUseSharedTabloGridCardWithDeleteAction(t *testing.T) {
repo := handlers.NewInMemoryAuthRepository()
authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
if err != nil {
t.Fatalf("expected demo user, got error: %v", err)
}
if _, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
OwnerID: authUser.ID,
Name: "Hello",
Status: handlers.TabloStatusInProgress,
}); err != nil {
t.Fatalf("expected tablo creation to succeed, got error: %v", err)
}
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newRouterWithHandler(handlers.NewAuthHandler(repo))
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
`class="project-card"`,
`class="project-date-row"`,
`hx-delete="/tablos/`,
} {
if !strings.Contains(body, want) {
t.Fatalf("expected home page to contain %q", want)
}
}
}
func TestHomePageProjectsCollapseAfterSixByDefault(t *testing.T) {
repo := handlers.NewInMemoryAuthRepository()
authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
if err != nil {
t.Fatalf("expected demo user, got error: %v", err)
}
for i := 0; i < 8; i++ {
if _, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
OwnerID: authUser.ID,
Name: "Project " + string(rune('A'+i)),
Status: handlers.TabloStatusTodo,
}); err != nil {
t.Fatalf("expected tablo creation to succeed, got error: %v", err)
}
}
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newRouterWithHandler(handlers.NewAuthHandler(repo))
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
if count := strings.Count(body, `class="project-card"`); count != 8 {
t.Fatalf("expected all 8 project cards to be rendered, got %d", count)
}
if count := strings.Count(body, `data-overview-project-hidden="true"`); count != 2 {
t.Fatalf("expected 2 project cards to be hidden by default, got %d", count)
}
for _, want := range []string{
`id="overview-projects-section"`,
`Voir 2 de plus`,
`data-overview-see-more="true"`,
`data-overview-expanded="false"`,
`data-overview-hidden-count="2"`,
`data-overview-hide-label="Masquer"`,
`data-overview-chevron-down="m6 9 6 6 6-6"`,
`data-overview-chevron-up="m6 15 6-6 6 6"`,
`data-overview-see-more-chevron="true"`,
`window.xtabloOverviewProjectsInitialized`,
} {
if !strings.Contains(body, want) {
t.Fatalf("expected home page to contain %q", want)
}
}
for _, unwanted := range []string{
`hx-get="/?show_projects=all"`,
`hx-target="#overview-projects-section"`,
} {
if strings.Contains(body, unwanted) {
t.Fatalf("expected home page to avoid HTMX see-more wiring %q", unwanted)
}
}
}
func TestHomePageProjectsUseVanillaJSSeeMoreToggle(t *testing.T) {
repo := handlers.NewInMemoryAuthRepository()
authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
if err != nil {
t.Fatalf("expected demo user, got error: %v", err)
}
for i := 0; i < 8; i++ {
if _, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
OwnerID: authUser.ID,
Name: "Project " + string(rune('A'+i)),
Status: handlers.TabloStatusTodo,
}); err != nil {
t.Fatalf("expected tablo creation to succeed, got error: %v", err)
}
}
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newRouterWithHandler(handlers.NewAuthHandler(repo))
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/?show_projects=all", nil)
req.Header.Set("HX-Request", "true")
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
`id="app-main-content"`,
`id="overview-projects-section"`,
`data-overview-see-more="true"`,
`data-overview-expanded="false"`,
`data-overview-project-hidden="true"`,
} {
if !strings.Contains(body, want) {
t.Fatalf("expected HTMX home response to contain %q", want)
}
}
for _, unwanted := range []string{
`hx-get="/?show_projects=all"`,
`hx-target="#overview-projects-section"`,
} {
if strings.Contains(body, unwanted) {
t.Fatalf("expected HTMX home response to avoid HTMX see-more wiring %q", unwanted)
}
}
}
func TestTasksPageReturnsHTMXMainContentSwap(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/tasks", nil)
req.Header.Set("HX-Request", "true")
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
`id="app-main-content"`,
`hx-swap-oob="outerHTML"`,
`id="sidebar-nav-tasks"`,
`data-current-view="kanban"`,
"Mes Tâches",
"Nouvelle tâche",
"Tableau",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected HTMX tasks response to contain %q", want)
}
}
if strings.Contains(body, `class="sidebar-nav-shell"`) {
t.Fatalf("expected HTMX tasks response to avoid rerendering the full sidebar")
}
}
func TestTasksRoutesCreatePatchAndDeleteThroughRouter(t *testing.T) {
repo := handlers.NewInMemoryAuthRepository()
authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
if err != nil {
t.Fatalf("expected demo user, got error: %v", err)
}
tablo, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
OwnerID: authUser.ID,
Name: "Router Tablo",
Color: "#3B82F6",
Status: handlers.TabloStatusTodo,
})
if err != nil {
t.Fatalf("expected tablo creation to succeed, got error: %v", err)
}
router := newRouterWithHandler(handlers.NewAuthHandler(repo))
sessionCookie := loginCookieForRouter(t, router)
createForm := url.Values{}
createForm.Set("tablo_id", tablo.ID.String())
createForm.Set("title", "Route Task")
createForm.Set("status", "todo")
createReq := httptest.NewRequest(http.MethodPost, "/tasks", strings.NewReader(createForm.Encode()))
createReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
createReq.AddCookie(sessionCookie)
createRec := httptest.NewRecorder()
router.ServeHTTP(createRec, createReq)
if createRec.Code != http.StatusOK {
t.Fatalf("expected task create status 200, got %d", createRec.Code)
}
if !strings.Contains(createRec.Body.String(), "Route Task") {
t.Fatalf("expected create response to contain task title, got %q", createRec.Body.String())
}
tasks, err := repo.ListTasksByTablo(context.Background(), handlers.ListTasksByTabloInput{
OwnerID: authUser.ID,
TabloID: tablo.ID,
})
if err != nil || len(tasks) != 1 {
t.Fatalf("expected one task after create, got %d / err=%v", len(tasks), err)
}
patchForm := url.Values{}
patchForm.Set("title", "Updated Route Task")
patchForm.Set("description", "Updated from router test")
patchForm.Set("status", "done")
patchReq := httptest.NewRequest(http.MethodPatch, "/tasks/"+tasks[0].ID.String(), strings.NewReader(patchForm.Encode()))
patchReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
patchReq.SetPathValue("taskID", tasks[0].ID.String())
patchReq.AddCookie(sessionCookie)
patchRec := httptest.NewRecorder()
router.ServeHTTP(patchRec, patchReq)
if patchRec.Code != http.StatusOK {
t.Fatalf("expected task patch status 200, got %d", patchRec.Code)
}
if !strings.Contains(patchRec.Body.String(), "Updated Route Task") {
t.Fatalf("expected patch response to contain updated title, got %q", patchRec.Body.String())
}
deleteReq := httptest.NewRequest(http.MethodDelete, "/tasks/"+tasks[0].ID.String(), nil)
deleteReq.SetPathValue("taskID", tasks[0].ID.String())
deleteReq.AddCookie(sessionCookie)
deleteRec := httptest.NewRecorder()
router.ServeHTTP(deleteRec, deleteReq)
if deleteRec.Code != http.StatusOK {
t.Fatalf("expected task delete status 200, got %d", deleteRec.Code)
}
if strings.Contains(deleteRec.Body.String(), "Updated Route Task") {
t.Fatalf("expected delete response to remove updated task, got %q", deleteRec.Body.String())
}
}
func TestTablosPageRendersFullDashboardPage(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/tablos", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
`class="sidebar-nav-shell"`,
`id="app-main-content" class="flex-1 overflow-auto"`,
"Mes Projets",
"Nouveau projet",
"Vue en grille",
"Rechercher...",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected tablos page to contain %q", want)
}
}
}
func TestTablosPageReturnsHTMXMainContentSwap(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/tablos?view=list&status=all", nil)
req.Header.Set("HX-Request", "true")
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
`id="app-main-content"`,
`hx-swap-oob="outerHTML"`,
`id="sidebar-nav-tablos"`,
"Mes Projets",
"Vue en liste",
} {
if !strings.Contains(body, want) {
t.Fatalf("expected HTMX tablos response to contain %q", want)
}
}
if strings.Contains(body, `class="sidebar-nav-shell"`) {
t.Fatalf("expected HTMX tablos response to avoid rerendering the full sidebar")
}
}
func TestTablosPageUtilityStylesExist(t *testing.T) {
content, err := os.ReadFile("static/tailwind.css")
if err != nil {
t.Fatalf("read tailwind.css: %v", err)
}
css := string(content)
for _, want := range []string{
".flex-1",
".overflow-auto",
".text-2xl",
".bg-purple-600",
".grid-cols-1",
".rounded-xl",
".md\\:flex-row",
".sm\\:grid-cols-2",
".lg\\:grid-cols-3",
".xl\\:grid-cols-4",
} {
if !strings.Contains(css, want) {
t.Fatalf("expected tailwind.css to contain utility %q", want)
}
}
}
func TestSignupCreatesUserSessionAndRedirects(t *testing.T) {
form := url.Values{}
form.Set("email", "new@xtablo.com")
form.Set("password", "xtablo-secret")
req := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
if redirect := rec.Header().Get("HX-Redirect"); redirect != "/" {
t.Fatalf("expected HX-Redirect to /, got %q", redirect)
}
sessionCookie := findCookie(rec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router.ServeHTTP(loginRec, loginReq)
if loginRec.Header().Get("HX-Redirect") != "/" {
t.Fatalf("expected signed up user to be able to log in")
}
}
func TestLogoutClearsSessionAndRedirectsToLogin(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
logoutReq := httptest.NewRequest(http.MethodPost, "/logout", nil)
logoutReq.AddCookie(sessionCookie)
logoutRec := httptest.NewRecorder()
router.ServeHTTP(logoutRec, logoutReq)
if logoutRec.Code != http.StatusSeeOther {
t.Fatalf("expected logout status 303, got %d", logoutRec.Code)
}
if location := logoutRec.Header().Get("Location"); location != "/login" {
t.Fatalf("expected logout redirect to /login, got %q", location)
}
clearedCookie := findCookie(logoutRec.Result().Cookies(), "xtablo_session")
if clearedCookie == nil {
t.Fatalf("expected cleared session cookie")
}
if clearedCookie.MaxAge >= 0 && clearedCookie.Value != "" {
t.Fatalf("expected cleared session cookie to be expired, got %+v", clearedCookie)
}
homeReq := httptest.NewRequest(http.MethodGet, "/", nil)
homeReq.AddCookie(sessionCookie)
homeRec := httptest.NewRecorder()
router.ServeHTTP(homeRec, homeReq)
if homeRec.Code != http.StatusSeeOther {
t.Fatalf("expected logged-out root access to redirect, got %d", homeRec.Code)
}
if location := homeRec.Header().Get("Location"); location != "/login" {
t.Fatalf("expected logged-out root redirect to /login, got %q", location)
}
}
func TestUnknownRouteShowsDashboard404WhenAuthenticated(t *testing.T) {
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router := newTestRouter()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
req := httptest.NewRequest(http.MethodGet, "/missing", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected status 404, got %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{
"Aperçu",
"Page introuvable",
"Cette page n'existe pas",
`href="/"`,
`action="/logout"`,
} {
if !strings.Contains(body, want) {
t.Fatalf("expected authenticated 404 page to contain %q", want)
}
}
}
func TestSessionSurvivesHandlerRecreation(t *testing.T) {
repo := handlers.NewInMemoryAuthRepository()
loginRouter := newRouterWithHandler(handlers.NewAuthHandler(repo))
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
loginRouter.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatalf("expected session cookie to be set")
}
reloadedRouter := newRouterWithHandler(handlers.NewAuthHandler(repo))
homeReq := httptest.NewRequest(http.MethodGet, "/", nil)
homeReq.AddCookie(sessionCookie)
homeRec := httptest.NewRecorder()
reloadedRouter.ServeHTTP(homeRec, homeReq)
if homeRec.Code != http.StatusOK {
t.Fatalf("expected session to survive handler recreation, got status %d", homeRec.Code)
}
}
func findCookie(cookies []*http.Cookie, name string) *http.Cookie {
for _, cookie := range cookies {
if cookie.Name == name {
return cookie
}
}
return nil
}
func loginCookieForRouter(t *testing.T, router http.Handler) *http.Cookie {
t.Helper()
form := url.Values{}
form.Set("email", "demo@xtablo.com")
form.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
router.ServeHTTP(loginRec, loginReq)
sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
if sessionCookie == nil {
t.Fatal("expected session cookie to be set")
}
return sessionCookie
}