Files
gitea-mobile/internal/handlers/handlers_test.go
T
agent-company 25bc305fc9 feat: add mobile-friendly HTTP 404 and 500 error pages
Add ErrorNotFound and ErrorInternal handler methods that render styled
error pages using the error.html template, with proper status codes,
responsive layout, SVG icons, and HTMX fragment support. Replace the
plain-text http.NotFound call in Dashboard with the new styled handler.

Closes leeworks-agents/gitea-mobile#131

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:06:10 +00:00

279 lines
7.3 KiB
Go

package handlers
import (
"net/http"
"net/http/httptest"
"testing"
"gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/config"
giteaclient "gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/gitea"
)
func newTestHandler() *Handler {
cfg := &config.Config{
GiteaURL: "https://gitea.example.com",
SessionSecret: "test-secret-that-is-at-least-32-chars-long",
ListenAddr: ":8080",
}
client := giteaclient.NewClient(cfg.GiteaURL)
return NewHandler(cfg, client)
}
func TestHealth(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
h.Health(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
if body := w.Body.String(); body != "ok\n" {
t.Errorf("body = %q, want %q", body, "ok\n")
}
}
func TestDashboard_NoToken(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
h.Dashboard(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
// Without a token in context, should show "No organizations found."
if body := w.Body.String(); body == "" {
t.Error("expected non-empty response body")
}
}
func TestDashboard_HTMX(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("HX-Request", "true")
w := httptest.NewRecorder()
h.Dashboard(w, req)
// HTMX request should not include full HTML page wrapper.
body := w.Body.String()
if body == "" {
t.Error("expected non-empty response body")
}
// Should NOT contain DOCTYPE for HTMX fragment.
if contains(body, "<!DOCTYPE") {
t.Error("HTMX response should not contain DOCTYPE")
}
}
func TestIsHTMX(t *testing.T) {
tests := []struct {
header string
want bool
}{
{"true", true},
{"false", false},
{"", false},
}
for _, tt := range tests {
req := httptest.NewRequest(http.MethodGet, "/", nil)
if tt.header != "" {
req.Header.Set("HX-Request", tt.header)
}
if got := isHTMX(req); got != tt.want {
t.Errorf("isHTMX(HX-Request=%q) = %v, want %v", tt.header, got, tt.want)
}
}
}
func TestCreateIssue_MissingFields(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodPost, "/issues", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
h.CreateIssue(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
}
func TestApplyLabels_InvalidIndex(t *testing.T) {
h := newTestHandler()
mux := http.NewServeMux()
mux.HandleFunc("POST /issues/{owner}/{repo}/{index}/labels", h.ApplyLabels)
req := httptest.NewRequest(http.MethodPost, "/issues/org/repo/abc/labels", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
}
func TestSubmitReview_MissingEventType(t *testing.T) {
h := newTestHandler()
mux := http.NewServeMux()
mux.HandleFunc("POST /pulls/{owner}/{repo}/{index}/review", h.SubmitReview)
req := httptest.NewRequest(http.MethodPost, "/pulls/org/repo/1/review", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
}
func TestSetIssueState_InvalidState(t *testing.T) {
h := newTestHandler()
mux := http.NewServeMux()
mux.HandleFunc("POST /issues/{owner}/{repo}/{index}/state", h.SetIssueState)
req := httptest.NewRequest(http.MethodPost, "/issues/org/repo/1/state", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
}
func TestSetIssueState_InvalidIndex(t *testing.T) {
h := newTestHandler()
mux := http.NewServeMux()
mux.HandleFunc("POST /issues/{owner}/{repo}/{index}/state", h.SetIssueState)
req := httptest.NewRequest(http.MethodPost, "/issues/org/repo/abc/state", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
}
func TestAddComment_EmptyBody(t *testing.T) {
h := newTestHandler()
mux := http.NewServeMux()
mux.HandleFunc("POST /issues/{owner}/{repo}/{index}/comments", h.AddComment)
req := httptest.NewRequest(http.MethodPost, "/issues/org/repo/1/comments", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
}
func TestErrorNotFound(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/nonexistent", nil)
w := httptest.NewRecorder()
h.ErrorNotFound(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d", w.Code, http.StatusNotFound)
}
body := w.Body.String()
if body == "" {
t.Error("expected non-empty response body")
}
if !contains(body, "404") {
t.Error("expected body to contain '404'")
}
if !contains(body, "Page Not Found") {
t.Error("expected body to contain 'Page Not Found'")
}
}
func TestErrorInternal(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/error", nil)
w := httptest.NewRecorder()
h.ErrorInternal(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", w.Code, http.StatusInternalServerError)
}
body := w.Body.String()
if body == "" {
t.Error("expected non-empty response body")
}
if !contains(body, "500") {
t.Error("expected body to contain '500'")
}
if !contains(body, "Internal Server Error") {
t.Error("expected body to contain 'Internal Server Error'")
}
}
func TestDashboard_NonRootPath_Returns404(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/unknown/path", nil)
w := httptest.NewRecorder()
h.Dashboard(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d", w.Code, http.StatusNotFound)
}
body := w.Body.String()
if !contains(body, "404") {
t.Error("expected body to contain '404' for non-root path")
}
}
func TestErrorNotFound_HTMX(t *testing.T) {
h := newTestHandler()
req := httptest.NewRequest(http.MethodGet, "/nonexistent", nil)
req.Header.Set("HX-Request", "true")
w := httptest.NewRecorder()
h.ErrorNotFound(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d", w.Code, http.StatusNotFound)
}
body := w.Body.String()
// HTMX response should not contain DOCTYPE.
if contains(body, "<!DOCTYPE") {
t.Error("HTMX response should not contain DOCTYPE")
}
if !contains(body, "Page Not Found") {
t.Error("expected body to contain 'Page Not Found'")
}
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && searchString(s, substr)
}
func searchString(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}