package server import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func doJSON(t *testing.T, srv *Server, method, path, body string, cookies ...*http.Cookie) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequestWithContext(context.Background(), method, path, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") for _, cookie := range cookies { req.AddCookie(cookie) } rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) return rec } func TestRegisterEndpointCreatesSession(t *testing.T) { srv, _ := newTestServer(t) rec := doJSON(t, srv, http.MethodPost, "/api/v1/auth/register", `{"username":"api_user","email":"api@example.com","password":"correct-horse-battery"}`) if rec.Code != http.StatusOK { t.Fatalf("status = %d, body = %s", rec.Code, rec.Body.String()) } var payload struct { User struct { ID string `json:"id"` Username string `json:"username"` DisplayName string `json:"display_name"` } `json:"user"` } if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { t.Fatalf("decode body: %v", err) } if payload.User.Username != "api_user" || payload.User.ID == "" { t.Fatalf("unexpected user payload: %s", rec.Body.String()) } cookies := rec.Result().Cookies() if len(cookies) == 0 || cookies[0].Name != sessionCookieName { t.Fatalf("session cookie is missing: %v", cookies) } if !cookies[0].HttpOnly { t.Fatal("session cookie must be HttpOnly") } // С полученной cookie доступен профиль. me := doJSON(t, srv, http.MethodGet, "/api/v1/users/@me", "", cookies[0]) if me.Code != http.StatusOK { t.Fatalf("GET /users/@me = %d, body = %s", me.Code, me.Body.String()) } } func TestLoginEndpointErrors(t *testing.T) { srv, _ := newTestServer(t) doJSON(t, srv, http.MethodPost, "/api/v1/auth/register", `{"username":"login_user","email":"login@example.com","password":"correct-horse-battery"}`) rec := doJSON(t, srv, http.MethodPost, "/api/v1/auth/login", `{"email":"login@example.com","password":"wrong-password"}`) if rec.Code != http.StatusUnauthorized { t.Fatalf("wrong password status = %d, want 401", rec.Code) } if !strings.Contains(rec.Body.String(), "invalid credentials") { t.Fatalf("unexpected error body: %s", rec.Body.String()) } rec = doJSON(t, srv, http.MethodPost, "/api/v1/auth/login", `{"email":"login@example.com","password":"correct-horse-battery"}`) if rec.Code != http.StatusOK { t.Fatalf("valid login status = %d, body = %s", rec.Code, rec.Body.String()) } } func TestAuthenticatedEndpointsRequireSession(t *testing.T) { srv, _ := newTestServer(t) for _, path := range []string{"/api/v1/users/@me", "/api/v1/auth/sessions"} { rec := doJSON(t, srv, http.MethodGet, path, "") if rec.Code != http.StatusUnauthorized { t.Fatalf("GET %s without session = %d, want 401", path, rec.Code) } } } func TestValidationRejectsShortPassword(t *testing.T) { srv, _ := newTestServer(t) rec := doJSON(t, srv, http.MethodPost, "/api/v1/auth/register", `{"username":"short_user","email":"short@example.com","password":"short"}`) if rec.Code == http.StatusOK { t.Fatalf("short password accepted: %s", rec.Body.String()) } } func TestOpenAPIDocumentsAuthEndpoints(t *testing.T) { srv, _ := newTestServer(t) rec := doJSON(t, srv, http.MethodGet, "/api/v1/openapi.json", "") if rec.Code != http.StatusOK { t.Fatalf("openapi status = %d", rec.Code) } var doc struct { Paths map[string]any `json:"paths"` } if err := json.Unmarshal(rec.Body.Bytes(), &doc); err != nil { t.Fatalf("decode openapi: %v", err) } paths := []string{ "/auth/register", "/auth/login", "/auth/logout", "/auth/sessions", "/users/@me", "/auth/2fa/setup", "/meta", // Ручки Фазы 1, сгенерированные huma. "/users/@me/guilds", "/guilds", "/guilds/{guild_id}", "/guilds/{guild_id}/channels", "/guilds/{guild_id}/members", "/guilds/{guild_id}/roles", "/instance", "/instance/settings", "/instance/guilds", "/instance/users", "/instance/audit", } for _, path := range paths { if _, ok := doc.Paths[path]; !ok { t.Errorf("openapi is missing %s", path) } } }