Files
agentic-coding-workflow/internal/user/handler_test.go
T

102 lines
3.4 KiB
Go

package user
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
// mountHandler builds a chi router with the user Handler attached. Helper
// is shared by every handler test so the route prefix /api/v1/auth lives in
// exactly one place; if the URL contract ever shifts the change is local.
func mountHandler(svc Service) http.Handler {
r := chi.NewRouter()
h := NewHandler(svc)
h.Mount(r)
return r
}
// TestHandler_LoginThenMeThenLogout walks the full happy path of the auth
// surface: log in, hit /me with the issued token, log out, and confirm the
// token is no longer accepted. It exercises the integration between Handler,
// Service, the in-memory fakeRepo and the Auth middleware.
func TestHandler_LoginThenMeThenLogout(t *testing.T) {
repo := newFakeRepo()
hash, err := HashPassword("password123")
require.NoError(t, err)
repo.users["a@b.c"] = &User{
ID: uuid.New(),
Email: "a@b.c",
DisplayName: "T",
PasswordHash: hash,
}
svc := NewService(repo, nil)
srv := httptest.NewServer(mountHandler(svc))
defer srv.Close()
// 1. login
body, err := json.Marshal(map[string]string{"email": "a@b.c", "password": "password123"})
require.NoError(t, err)
resp, err := http.Post(srv.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var loginOut struct {
Token string `json:"token"`
User struct {
ID string `json:"id"`
Email string `json:"email"`
} `json:"user"`
}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&loginOut))
require.NoError(t, resp.Body.Close())
require.NotEmpty(t, loginOut.Token)
require.Equal(t, "a@b.c", loginOut.User.Email)
// 2. me
req, err := http.NewRequest(http.MethodGet, srv.URL+"/api/v1/auth/me", nil)
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer "+loginOut.Token)
resp, err = http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close())
// 3. logout
req, err = http.NewRequest(http.MethodPost, srv.URL+"/api/v1/auth/logout", nil)
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer "+loginOut.Token)
resp, err = http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusNoContent, resp.StatusCode)
require.NoError(t, resp.Body.Close())
// 4. me again -> 401: the same bearer must be rejected after logout.
req, err = http.NewRequest(http.MethodGet, srv.URL+"/api/v1/auth/me", nil)
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer "+loginOut.Token)
resp, err = http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
require.NoError(t, resp.Body.Close())
}
// TestHandler_LoginInvalidPayload covers the validation branch: an empty
// JSON object lacks both email and password and must be rejected with 400
// before the service layer is consulted.
func TestHandler_LoginInvalidPayload(t *testing.T) {
svc := NewService(newFakeRepo(), nil)
srv := httptest.NewServer(mountHandler(svc))
defer srv.Close()
resp, err := http.Post(srv.URL+"/api/v1/auth/login", "application/json", bytes.NewReader([]byte("{}")))
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
require.NoError(t, resp.Body.Close())
}