| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- package handlers
- import (
- "context"
- "log/slog"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // MockFeedbagRetriever is a mock implementation of FeedbagRetriever
- type MockFeedbagRetriever struct {
- mock.Mock
- }
- func (m *MockFeedbagRetriever) RetrieveFeedbag(ctx context.Context, screenName state.IdentScreenName) ([]wire.FeedbagItem, error) {
- args := m.Called(ctx, screenName)
- if items := args.Get(0); items != nil {
- return items.([]wire.FeedbagItem), args.Error(1)
- }
- return nil, args.Error(1)
- }
- func (m *MockFeedbagRetriever) RelationshipsByUser(ctx context.Context, screenName state.IdentScreenName) ([]state.IdentScreenName, error) {
- args := m.Called(ctx, screenName)
- if names := args.Get(0); names != nil {
- return names.([]state.IdentScreenName), args.Error(1)
- }
- return nil, args.Error(1)
- }
- // MockBuddyBroadcaster is a mock implementation of BuddyBroadcaster
- type MockBuddyBroadcaster struct {
- mock.Mock
- }
- func (m *MockBuddyBroadcaster) BroadcastBuddyArrived(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error {
- args := m.Called(ctx, screenName, userInfo)
- return args.Error(0)
- }
- func (m *MockBuddyBroadcaster) BroadcastBuddyDeparted(ctx context.Context, instance *state.SessionInstance) error {
- args := m.Called(ctx, instance)
- return args.Error(0)
- }
- // MockProfileManager is a mock implementation of ProfileManager
- type MockProfileManager struct {
- mock.Mock
- }
- func (m *MockProfileManager) SetProfile(ctx context.Context, screenName state.IdentScreenName, profile state.UserProfile) error {
- args := m.Called(ctx, screenName, profile)
- return args.Error(0)
- }
- func (m *MockProfileManager) Profile(ctx context.Context, screenName state.IdentScreenName) (state.UserProfile, error) {
- args := m.Called(ctx, screenName)
- return args.Get(0).(state.UserProfile), args.Error(1)
- }
- func TestPresenceHandler_GetPresence(t *testing.T) {
- tests := []struct {
- name string
- queryParams string
- setupMocks func(*MockSessionRetriever, *MockFeedbagRetriever, *MockRelationshipFetcher)
- expectedStatusCode int
- checkResponse func(*testing.T, string)
- }{
- {
- name: "Success_BuddyList",
- queryParams: "bl=1",
- setupMocks: func(sr *MockSessionRetriever, fr *MockFeedbagRetriever, rf *MockRelationshipFetcher) {
- // Return feedbag with a group and buddy
- fr.On("RetrieveFeedbag", mock.Anything, state.NewIdentScreenName("testuser")).
- Return([]wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, Name: "Friends", GroupID: 0},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, Name: "buddy1", GroupID: 1},
- }, nil)
- rf.On("Relationship", mock.Anything, state.NewIdentScreenName("testuser"), state.NewIdentScreenName("buddy1")).
- Return(state.Relationship{}, nil)
- sr.On("RetrieveSession", state.NewIdentScreenName("buddy1")).
- Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"groups"`)
- assert.Contains(t, body, `"Friends"`)
- assert.Contains(t, body, `"buddy1"`)
- assert.Contains(t, body, `"offline"`)
- },
- },
- {
- name: "Success_TargetUsers",
- queryParams: "t=user1,user2",
- setupMocks: func(sr *MockSessionRetriever, fr *MockFeedbagRetriever, rf *MockRelationshipFetcher) {
- rf.On("Relationship", mock.Anything, state.NewIdentScreenName("testuser"), state.NewIdentScreenName("user1")).
- Return(state.Relationship{}, nil)
- rf.On("Relationship", mock.Anything, state.NewIdentScreenName("testuser"), state.NewIdentScreenName("user2")).
- Return(state.Relationship{}, nil)
- sr.On("RetrieveSession", state.NewIdentScreenName("user1")).
- Return(nil)
- sr.On("RetrieveSession", state.NewIdentScreenName("user2")).
- Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"users"`)
- assert.Contains(t, body, `"user1"`)
- assert.Contains(t, body, `"user2"`)
- },
- },
- {
- name: "Success_BlockedUserOffline",
- queryParams: "t=blockeduser",
- setupMocks: func(sr *MockSessionRetriever, fr *MockFeedbagRetriever, rf *MockRelationshipFetcher) {
- rf.On("Relationship", mock.Anything, state.NewIdentScreenName("testuser"), state.NewIdentScreenName("blockeduser")).
- Return(state.Relationship{YouBlock: true}, nil)
- // RetrieveSession should NOT be called for blocked users
- },
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"blockeduser"`)
- assert.Contains(t, body, `"offline"`)
- },
- },
- {
- name: "Success_EmptyRequest",
- queryParams: "",
- setupMocks: func(sr *MockSessionRetriever, fr *MockFeedbagRetriever, rf *MockRelationshipFetcher) {},
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- sessionRetriever := &MockSessionRetriever{}
- feedbagRetriever := &MockFeedbagRetriever{}
- relFetcher := &MockRelationshipFetcher{}
- sessionMgr, aimsid := createTestSessionManager("testuser")
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- SessionRetriever: sessionRetriever,
- FeedbagRetriever: feedbagRetriever,
- RelationshipFetcher: relFetcher,
- Logger: slog.Default(),
- }
- tt.setupMocks(sessionRetriever, feedbagRetriever, relFetcher)
- reqURL := "/presence/get?aimsid=" + aimsid
- if tt.queryParams != "" {
- reqURL += "&" + tt.queryParams
- }
- req, err := http.NewRequest("GET", reqURL, nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.GetPresence(rr, req)
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- if tt.checkResponse != nil {
- responseBody := strings.TrimSpace(rr.Body.String())
- tt.checkResponse(t, responseBody)
- }
- sessionRetriever.AssertExpectations(t)
- feedbagRetriever.AssertExpectations(t)
- relFetcher.AssertExpectations(t)
- })
- }
- }
- func TestPresenceHandler_GetPresence_MissingAimsid(t *testing.T) {
- handler := &PresenceHandler{
- SessionManager: state.NewWebAPISessionManager(),
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/get", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.GetPresence(rr, req)
- assert.Equal(t, http.StatusBadRequest, rr.Code)
- assert.Contains(t, rr.Body.String(), "missing aimsid parameter")
- }
- func TestPresenceHandler_GetPresence_SessionNotFound(t *testing.T) {
- handler := &PresenceHandler{
- SessionManager: state.NewWebAPISessionManager(),
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/get?aimsid=nonexistent", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.GetPresence(rr, req)
- assert.Equal(t, http.StatusNotFound, rr.Code)
- assert.Contains(t, rr.Body.String(), "session not found")
- }
- func TestPresenceHandler_SetState_MissingAimsid(t *testing.T) {
- handler := &PresenceHandler{
- SessionManager: state.NewWebAPISessionManager(),
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/setState", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.SetState(rr, req)
- assert.Equal(t, http.StatusBadRequest, rr.Code)
- assert.Contains(t, rr.Body.String(), "missing aimsid parameter")
- }
- func TestPresenceHandler_SetState_InvalidState(t *testing.T) {
- sessionMgr, aimsid := createTestSessionManager("testuser")
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/setState?aimsid="+aimsid+"&state=bogus", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.SetState(rr, req)
- // Web-only sessions (no OSCAR session) return 200 before checking state param
- // because the handler returns early with success for web-only sessions
- assert.Equal(t, http.StatusOK, rr.Code)
- }
- func TestPresenceHandler_SetState_WebOnlySession(t *testing.T) {
- // Web-only sessions (no OSCAR session) should return success
- sessionMgr, aimsid := createTestSessionManager("testuser")
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/setState?aimsid="+aimsid+"&state=online", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.SetState(rr, req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), `"statusCode":200`)
- }
- func TestIsICQScreenName(t *testing.T) {
- tests := []struct {
- name string
- screenName string
- expected bool
- }{
- {"ICQ_Number", "123456789", true},
- {"AIM_Name", "cooluser", false},
- {"AIM_WithNumbers", "cool123", false},
- {"Empty", "", false},
- {"Single_Digit", "5", true},
- {"Mixed_Chars", "12abc34", false},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, tt.expected, isICQScreenName(tt.screenName))
- })
- }
- }
- func TestPresenceHandler_Icon(t *testing.T) {
- tests := []struct {
- name string
- queryParams string
- setupMocks func(*MockSessionRetriever)
- expectedStatusCode int
- checkRedirect func(*testing.T, *httptest.ResponseRecorder)
- }{
- {
- name: "Redirect_OfflineUser",
- queryParams: "name=offlineuser",
- setupMocks: func(sr *MockSessionRetriever) {
- sr.On("RetrieveSession", state.NewIdentScreenName("offlineuser")).Return(nil)
- },
- expectedStatusCode: http.StatusFound,
- checkRedirect: func(t *testing.T, rr *httptest.ResponseRecorder) {
- location := rr.Header().Get("Location")
- assert.Contains(t, location, "offline")
- },
- },
- {
- name: "Error_MissingName",
- queryParams: "",
- setupMocks: func(sr *MockSessionRetriever) {},
- expectedStatusCode: http.StatusBadRequest,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- sessionRetriever := &MockSessionRetriever{}
- handler := &PresenceHandler{
- SessionRetriever: sessionRetriever,
- Logger: slog.Default(),
- }
- tt.setupMocks(sessionRetriever)
- reqURL := "/presence/icon"
- if tt.queryParams != "" {
- reqURL += "?" + tt.queryParams
- }
- req, err := http.NewRequest("GET", reqURL, nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.Icon(rr, req)
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- if tt.checkRedirect != nil {
- tt.checkRedirect(t, rr)
- }
- sessionRetriever.AssertExpectations(t)
- })
- }
- }
- func TestPresenceHandler_SetProfile(t *testing.T) {
- tests := []struct {
- name string
- queryParams string
- setupMocks func(*MockProfileManager)
- expectedStatusCode int
- checkResponse func(*testing.T, string)
- }{
- {
- name: "Success_SetProfile",
- queryParams: "profile=Hello+World",
- setupMocks: func(pm *MockProfileManager) {
- pm.On("SetProfile", mock.Anything, state.NewIdentScreenName("testuser"), mock.AnythingOfType("state.UserProfile")).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- },
- },
- {
- name: "Error_ProfileTooLarge",
- queryParams: "profile=" + strings.Repeat("x", 4097),
- setupMocks: func(pm *MockProfileManager) {},
- expectedStatusCode: http.StatusBadRequest,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "profile too large")
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- profileManager := &MockProfileManager{}
- sessionMgr, aimsid := createTestSessionManager("testuser")
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- ProfileManager: profileManager,
- Logger: slog.Default(),
- }
- tt.setupMocks(profileManager)
- reqURL := "/presence/setProfile?aimsid=" + aimsid + "&" + tt.queryParams
- req, err := http.NewRequest("GET", reqURL, nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.SetProfile(rr, req)
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- if tt.checkResponse != nil {
- responseBody := strings.TrimSpace(rr.Body.String())
- tt.checkResponse(t, responseBody)
- }
- profileManager.AssertExpectations(t)
- })
- }
- }
- func TestPresenceHandler_GetProfile(t *testing.T) {
- profileManager := &MockProfileManager{}
- sessionMgr, aimsid := createTestSessionManager("testuser")
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- ProfileManager: profileManager,
- Logger: slog.Default(),
- }
- profileManager.On("Profile", mock.Anything, state.NewIdentScreenName("testuser")).
- Return(state.UserProfile{ProfileText: "My profile"}, nil)
- req, err := http.NewRequest("GET", "/presence/getProfile?aimsid="+aimsid, nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.GetProfile(rr, req)
- assert.Equal(t, http.StatusOK, rr.Code)
- body := rr.Body.String()
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"My profile"`)
- assert.Contains(t, body, `"testuser"`)
- profileManager.AssertExpectations(t)
- }
|