package handler import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/service" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestListMySessions_Success(t *testing.T) { now := time.Date(2026, 4, 2, 1, 2, 3, 0, time.UTC) setDefaultHTTPClientForTest(t, roundTripFunc(func(r *http.Request) (*http.Response, error) { if r.URL.Path == "/sessions/whoami" { return httpJSONAny(r, http.StatusOK, map[string]any{ "id": "current-sid", "authenticated_at": now.Format(time.RFC3339), "identity": map[string]any{ "id": "user-123", "traits": map[string]any{ "email": "user@example.com", "name": "User", "role": "user", }, }, }), nil } return httpResponse(r, http.StatusNotFound, "not found"), nil })) mockKratos := new(MockKratosAdminService) mockKratos.On("ListIdentitySessions", mock.Anything, "user-123").Return([]service.KratosSession{ { ID: "current-sid", Active: true, AuthenticatedAt: now, ExpiresAt: now.Add(24 * time.Hour), }, { ID: "other-sid", Active: true, AuthenticatedAt: now.Add(-2 * time.Hour), ExpiresAt: now.Add(22 * time.Hour), }, }, nil).Once() auditRepo := &mockAuditRepo{ logs: []domain.AuditLog{ { UserID: "user-123", EventType: "login_success", SessionID: "other-sid", Timestamp: now.Add(-30 * time.Minute), IPAddress: "203.0.113.10", UserAgent: "Mozilla/5.0", }, }, } h := &AuthHandler{ KratosAdmin: mockKratos, AuditRepo: auditRepo, } app := fiber.New() app.Get("/api/v1/user/sessions", h.ListMySessions) req := httptest.NewRequest(http.MethodGet, "/api/v1/user/sessions", nil) req.Header.Set("Cookie", "ory_kratos_session=valid") resp, err := app.Test(req, -1) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) var body struct { Items []struct { SessionID string `json:"session_id"` IsCurrent bool `json:"is_current"` IsActive bool `json:"is_active"` IPAddress string `json:"ip_address"` UserAgent string `json:"user_agent"` } `json:"items"` } err = json.NewDecoder(resp.Body).Decode(&body) assert.NoError(t, err) if assert.Len(t, body.Items, 2) { assert.Equal(t, "current-sid", body.Items[0].SessionID) assert.True(t, body.Items[0].IsCurrent) assert.Equal(t, "other-sid", body.Items[1].SessionID) assert.True(t, body.Items[1].IsActive) assert.Equal(t, "203.0.113.10", body.Items[1].IPAddress) assert.Equal(t, "Mozilla/5.0", body.Items[1].UserAgent) } mockKratos.AssertExpectations(t) } func TestDeleteMySession_Success(t *testing.T) { setDefaultHTTPClientForTest(t, roundTripFunc(func(r *http.Request) (*http.Response, error) { if r.URL.Path == "/sessions/whoami" { return httpJSONAny(r, http.StatusOK, map[string]any{ "id": "current-sid", "authenticated_at": time.Now().UTC().Format(time.RFC3339), "identity": map[string]any{ "id": "user-123", "traits": map[string]any{ "email": "user@example.com", "name": "User", "role": "user", }, }, }), nil } return httpResponse(r, http.StatusNotFound, "not found"), nil })) mockKratos := new(MockKratosAdminService) mockKratos.On("ListIdentitySessions", mock.Anything, "user-123").Return([]service.KratosSession{ {ID: "target-sid", Active: true}, }, nil).Once() mockKratos.On("GetSession", mock.Anything, "target-sid").Return(&service.KratosSession{ ID: "target-sid", Active: true, }, nil).Once() mockKratos.On("DeleteSession", mock.Anything, "target-sid").Return(nil).Once() auditRepo := &mockAuditRepo{} h := &AuthHandler{ KratosAdmin: mockKratos, AuditRepo: auditRepo, } app := fiber.New() app.Delete("/api/v1/user/sessions/:id", h.DeleteMySession) req := httptest.NewRequest(http.MethodDelete, "/api/v1/user/sessions/target-sid", nil) req.Header.Set("Cookie", "ory_kratos_session=valid") req.Header.Set("User-Agent", "session-test-agent") resp, err := app.Test(req, -1) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) if assert.Len(t, auditRepo.logs, 1) { assert.Equal(t, "session.revoked", auditRepo.logs[0].EventType) assert.Equal(t, "user-123", auditRepo.logs[0].UserID) assert.Equal(t, "current-sid", auditRepo.logs[0].SessionID) assert.Contains(t, auditRepo.logs[0].Details, "target-sid") } mockKratos.AssertExpectations(t) }