package handler import ( "context" "errors" "net/http" "net/http/httptest" "testing" "5803025015/w04/internal/auth" "5803025015/w04/internal/middleware" "5803025015/w04/internal/models" ) // ---------- GetProfile ---------- func TestGetProfile(t *testing.T) { t.Run("with user id in context -> 200", func(t *testing.T) { h, _, _ := setupAuthHandler() req := httptest.NewRequest(http.MethodGet, "/profile", nil) ctx := context.WithValue(req.Context(), middleware.UserIDKey, "u1") req = req.WithContext(ctx) rec := httptest.NewRecorder() h.GetProfile(rec, req) if rec.Code != http.StatusOK { t.Fatalf("want 200, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("without user id -> 401", func(t *testing.T) { h, _, _ := setupAuthHandler() req := httptest.NewRequest(http.MethodGet, "/profile", nil) rec := httptest.NewRecorder() h.GetProfile(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("want 401, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) } // ---------- Method dispatch ---------- func TestSnippetsDispatch(t *testing.T) { t.Run("GET dispatch", func(t *testing.T) { h, _ := setupSnippetHandler() rec := doSnippetRequest(h.Snippets, http.MethodGet, "/snippets", nil, "u1") if rec.Code != http.StatusOK { t.Fatalf("want 200, got %d", rec.Code) } }) t.Run("POST dispatch", func(t *testing.T) { h, _ := setupSnippetHandler() rec := doSnippetRequest(h.Snippets, http.MethodPost, "/snippets", []byte(`{"title":"t","content":"c"}`), "u1") if rec.Code != http.StatusCreated { t.Fatalf("want 201, got %d", rec.Code) } }) t.Run("PUT -> 405", func(t *testing.T) { h, _ := setupSnippetHandler() rec := doSnippetRequest(h.Snippets, http.MethodPut, "/snippets", nil, "u1") if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("want 405, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) } func TestSnippetByIDDispatch(t *testing.T) { t.Run("GET dispatch", func(t *testing.T) { h, store := setupSnippetHandler() _ = store.Create(nil, &models.Snippet{ID: "s1", Title: "t", Content: "c", UserID: "u1"}) rec := doSnippetRequest(h.SnippetByID, http.MethodGet, "/snippets/s1", nil, "") if rec.Code != http.StatusOK { t.Fatalf("want 200, got %d", rec.Code) } }) t.Run("DELETE dispatch", func(t *testing.T) { h, store := setupSnippetHandler() _ = store.Create(nil, &models.Snippet{ID: "s1", Title: "t", Content: "c", UserID: "u1"}) rec := doSnippetRequest(h.SnippetByID, http.MethodDelete, "/snippets/s1", nil, "u1") if rec.Code != http.StatusNoContent { t.Fatalf("want 204, got %d", rec.Code) } }) t.Run("PUT -> 405", func(t *testing.T) { h, _ := setupSnippetHandler() rec := doSnippetRequest(h.SnippetByID, http.MethodPut, "/snippets/s1", nil, "u1") if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("want 405, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) } // ---------- Auth handler error branches ---------- func TestAuthErrorBranches(t *testing.T) { t.Run("Register: store non-duplicate error -> 500", func(t *testing.T) { h, us, _ := setupAuthHandler() us.createErr = errors.New("boom") rec := doRequest(h.Register, http.MethodPost, "/register", []byte(`{"username":"alice","password":"secret123"}`), nil) if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Login: session store create error -> 500", func(t *testing.T) { h, us, ss := setupAuthHandler() hash, _ := auth.HashPassword("secret123") _ = us.Create(nil, &models.User{ID: "u1", Username: "alice", PasswordHash: hash}) ss.createErr = errors.New("boom") rec := doRequest(h.Login, http.MethodPost, "/login", []byte(`{"username":"alice","password":"secret123"}`), nil) if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Logout: delete error -> 500", func(t *testing.T) { h, _, ss := setupAuthHandler() _ = ss.Create(nil, &models.Session{Token: "tok", UserID: "u1"}) ss.deleteErr = errors.New("boom") rec := doRequest(h.Logout, http.MethodPost, "/logout", nil, map[string]string{"Authorization": "Bearer tok"}) if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Logout: Basic scheme -> 401", func(t *testing.T) { h, _, _ := setupAuthHandler() rec := doRequest(h.Logout, http.MethodPost, "/logout", nil, map[string]string{"Authorization": "Basic abc"}) if rec.Code != http.StatusUnauthorized { t.Fatalf("want 401, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) } // ---------- Snippet handler error branches ---------- func TestSnippetErrorBranches(t *testing.T) { t.Run("Create: store error -> 500", func(t *testing.T) { h, store := setupSnippetHandler() store.createErr = errors.New("boom") rec := doSnippetRequest(h.Create, http.MethodPost, "/snippets", []byte(`{"title":"t","content":"c"}`), "u1") if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("List: store error -> 500", func(t *testing.T) { h, store := setupSnippetHandler() store.listErr = errors.New("boom") rec := doSnippetRequest(h.List, http.MethodGet, "/snippets", nil, "u1") if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Get: store non-NotFound error -> 500", func(t *testing.T) { h, store := setupSnippetHandler() store.getErr = errors.New("boom") rec := doSnippetRequest(h.Get, http.MethodGet, "/snippets/s1", nil, "") if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Delete: GetByID store error -> 500", func(t *testing.T) { h, store := setupSnippetHandler() store.getErr = errors.New("boom") rec := doSnippetRequest(h.Delete, http.MethodDelete, "/snippets/s1", nil, "u1") if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Delete: Delete store error -> 500", func(t *testing.T) { h, store := setupSnippetHandler() _ = store.Create(nil, &models.Snippet{ID: "s1", Title: "t", Content: "c", UserID: "u1"}) store.deleteErr = errors.New("boom") rec := doSnippetRequest(h.Delete, http.MethodDelete, "/snippets/s1", nil, "u1") if rec.Code != http.StatusInternalServerError { t.Fatalf("want 500, got %d", rec.Code) } assertContentTypeJSON(t, rec) }) t.Run("Delete: missing id -> 400", func(t *testing.T) { h, _ := setupSnippetHandler() req := httptest.NewRequest(http.MethodDelete, "/snippets/", nil) req = req.WithContext(context.WithValue(req.Context(), middleware.UserIDKey, "u1")) rec := httptest.NewRecorder() h.Delete(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("want 400, got %d", rec.Code) } }) } // ---------- extractID PathValue branch ---------- func TestExtractIDWithPathValue(t *testing.T) { t.Run("uses PathValue when set", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/snippets/", nil) req.SetPathValue("id", "abc-123") if got := extractID(req); got != "abc-123" { t.Fatalf("want abc-123, got %q", got) } }) t.Run("falls back to path parsing", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/snippets/xyz/", nil) if got := extractID(req); got != "xyz" { t.Fatalf("want xyz, got %q", got) } }) }