You've already forked agentic-coding-workflow
feat(user): http handler for login/me/logout with middleware integration
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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)
|
||||
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())
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user