Files
glchat/internal/server/auth_test.go
T

127 lines
4.1 KiB
Go
Raw Normal View History

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)
}
}
}