875 lines
26 KiB
Go
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
|
|
}
|