| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- package handlers
- import (
- "context"
- "encoding/json"
- "log/slog"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // MockFeedbagService is a mock implementation of FeedbagService
- type MockFeedbagService struct {
- mock.Mock
- }
- func (m *MockFeedbagService) DeleteItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem) (*wire.SNACMessage, error) {
- args := m.Called(ctx, instance, inFrame, inBody)
- if msg := args.Get(0); msg != nil {
- return msg.(*wire.SNACMessage), args.Error(1)
- }
- return nil, args.Error(1)
- }
- func (m *MockFeedbagService) Query(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) (wire.SNACMessage, error) {
- args := m.Called(ctx, instance, inFrame)
- return args.Get(0).(wire.SNACMessage), args.Error(1)
- }
- func (m *MockFeedbagService) QueryIfModified(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error) {
- args := m.Called(ctx, instance, inFrame, inBody)
- return args.Get(0).(wire.SNACMessage), args.Error(1)
- }
- func (m *MockFeedbagService) RespondAuthorizeToHost(ctx context.Context, instance state.IdentScreenName, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error {
- args := m.Called(ctx, instance, inFrame, inBody)
- return args.Error(0)
- }
- func (m *MockFeedbagService) RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
- args := m.Called(ctx, inFrame)
- return args.Get(0).(wire.SNACMessage)
- }
- func (m *MockFeedbagService) StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster) {
- m.Called(ctx, instance, inFrame, inBody)
- }
- func (m *MockFeedbagService) EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) error {
- args := m.Called(ctx, instance, inFrame)
- return args.Error(0)
- }
- func (m *MockFeedbagService) UpsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem) (*wire.SNACMessage, error) {
- args := m.Called(ctx, instance, inFrame, items)
- if msg := args.Get(0); msg != nil {
- return msg.(*wire.SNACMessage), args.Error(1)
- }
- return nil, args.Error(1)
- }
- func (m *MockFeedbagService) Use(ctx context.Context, instance *state.SessionInstance) error {
- args := m.Called(ctx, instance)
- return args.Error(0)
- }
- // 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, screenName state.IdentScreenName) error {
- args := m.Called(ctx, screenName)
- return args.Error(0)
- }
- // onlineUserInfoReply builds a locate UserInfoReply for an online user,
- // optionally marking them idle by the given number of minutes (0 = not idle).
- func onlineUserInfoReply(screenName string, idleMinutes uint16) wire.SNACMessage {
- info := wire.TLVUserInfo{ScreenName: screenName}
- if idleMinutes > 0 {
- info.Append(wire.NewTLVBE(wire.OServiceUserInfoIdleTime, idleMinutes))
- }
- return wire.SNACMessage{
- Body: wire.SNAC_0x02_0x06_LocateUserInfoReply{TLVUserInfo: info},
- }
- }
- // screenNameMatcher matches a UserInfoQuery request body by its target screen name.
- func screenNameMatcher(screenName string) any {
- return mock.MatchedBy(func(b wire.SNAC_0x02_0x05_LocateUserInfoQuery) bool {
- return b.ScreenName == screenName
- })
- }
- func TestPresenceHandler_GetPresence(t *testing.T) {
- tests := []struct {
- name string
- queryParams string
- setupMocks func(*MockFeedbagService, *MockLocateService)
- expectedStatusCode int
- checkResponse func(*testing.T, string)
- }{
- {
- name: "Success_BuddyList",
- queryParams: "bl=1",
- setupMocks: func(fr *MockFeedbagService, ls *MockLocateService) {
- // Return feedbag with a group and buddy
- fr.On("Query", mock.Anything, mock.Anything, mock.Anything).
- Return(wire.SNACMessage{
- Body: wire.SNAC_0x13_0x06_FeedbagReply{
- Items: []wire.FeedbagItem{
- {ItemID: 0, ClassID: wire.FeedbagClassIdGroup, Name: "Friends", GroupID: 1},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, Name: "buddy1", GroupID: 1},
- },
- },
- }, nil)
- ls.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("buddy1")).
- Return(onlineUserInfoReply("buddy1", 0), 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, `"online"`)
- },
- },
- {
- name: "Success_TargetUsers",
- queryParams: "t=user1,user2",
- setupMocks: func(fr *MockFeedbagService, ls *MockLocateService) {
- ls.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("user1")).
- Return(onlineUserInfoReply("user1", 0), nil)
- // user2 is idle for 7 minutes.
- ls.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("user2")).
- Return(onlineUserInfoReply("user2", 7), 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"`)
- assert.Contains(t, body, `"idle"`)
- },
- },
- {
- name: "Success_BlockedOrOfflineUser",
- queryParams: "t=blockeduser",
- setupMocks: func(fr *MockFeedbagService, ls *MockLocateService) {
- // A blocked or offline user comes back as a locate error.
- ls.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("blockeduser")).
- Return(wire.SNACMessage{Body: wire.SNACError{Code: wire.ErrorCodeNotLoggedOn}}, nil)
- },
- 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(fr *MockFeedbagService, ls *MockLocateService) {},
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- },
- },
- {
- name: "Error_TooManyTargets",
- queryParams: "t=u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11",
- // No UserInfoQuery should be issued; the request is rejected up front.
- setupMocks: func(fr *MockFeedbagService, ls *MockLocateService) {},
- expectedStatusCode: http.StatusBadRequest,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "too many screen names requested")
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- feedbagService := &MockFeedbagService{}
- locateService := &MockLocateService{}
- oscarInstance := state.NewSession().AddInstance()
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- FeedbagService: feedbagService,
- LocateService: locateService,
- Logger: slog.Default(),
- }
- tt.setupMocks(feedbagService, locateService)
- // Presence payloads carry the viewer's alias, so GetPresence reads the
- // feedbag. Registered last so a case's own Query stub takes precedence.
- feedbagService.On("Query", mock.Anything, mock.Anything, mock.Anything).
- Return(wire.SNACMessage{Body: wire.SNAC_0x13_0x06_FeedbagReply{}}, nil).Maybe()
- 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()
- requireSession(handler.SessionManager, handler.GetPresence).ServeHTTP(rr, req)
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- if tt.checkResponse != nil {
- responseBody := strings.TrimSpace(rr.Body.String())
- tt.checkResponse(t, responseBody)
- }
- feedbagService.AssertExpectations(t)
- locateService.AssertExpectations(t)
- })
- }
- }
- // TestPresenceHandler_GetPresence_BuddyListGrouping verifies that bl=1 places
- // each buddy under its own group using realistic feedbag data, where group rows
- // carry ItemID 0 and a distinct nonzero GroupID, and buddy rows reference those
- // GroupIDs. This is the shape the OSCAR feedbag actually stores.
- func TestPresenceHandler_GetPresence_BuddyListGrouping(t *testing.T) {
- feedbagService := &MockFeedbagService{}
- locateService := &MockLocateService{}
- items := []wire.FeedbagItem{
- // Root order group: ItemID 0, GroupID 0, empty name — not a real buddy group.
- {ItemID: 0, GroupID: 0, ClassID: wire.FeedbagClassIdGroup, Name: ""},
- // Named groups: ItemID 0, distinct nonzero GroupIDs.
- {ItemID: 0, GroupID: 10, ClassID: wire.FeedbagClassIdGroup, Name: "Friends"},
- {ItemID: 0, GroupID: 20, ClassID: wire.FeedbagClassIdGroup, Name: "Work"},
- // Buddies reference their group's GroupID.
- {ItemID: 101, GroupID: 10, ClassID: wire.FeedbagClassIdBuddy, Name: "alice"},
- {ItemID: 201, GroupID: 20, ClassID: wire.FeedbagClassIdBuddy, Name: "bob"},
- }
- feedbagService.On("Query", mock.Anything, mock.Anything, mock.Anything).
- Return(wire.SNACMessage{Body: wire.SNAC_0x13_0x06_FeedbagReply{Items: items}}, nil)
- locateService.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("alice")).
- Return(onlineUserInfoReply("alice", 0), nil)
- locateService.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("bob")).
- Return(onlineUserInfoReply("bob", 0), nil)
- oscarInstance := state.NewSession().AddInstance()
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- FeedbagService: feedbagService,
- LocateService: locateService,
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/get?aimsid="+aimsid+"&bl=1", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- requireSession(handler.SessionManager, handler.GetPresence).ServeHTTP(rr, req)
- assert.Equal(t, http.StatusOK, rr.Code)
- var parsed struct {
- Response struct {
- Data struct {
- Groups []struct {
- Name string `json:"name"`
- Buddies []struct {
- AimID string `json:"aimId"`
- } `json:"buddies"`
- } `json:"groups"`
- } `json:"data"`
- } `json:"response"`
- }
- assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &parsed))
- // Build name -> set of buddy aimIds.
- byGroup := map[string][]string{}
- for _, g := range parsed.Response.Data.Groups {
- for _, b := range g.Buddies {
- byGroup[g.Name] = append(byGroup[g.Name], b.AimID)
- }
- }
- // Exactly the two named groups appear; the root group is excluded.
- assert.Len(t, parsed.Response.Data.Groups, 2)
- assert.Equal(t, []string{"alice"}, byGroup["Friends"])
- assert.Equal(t, []string{"bob"}, byGroup["Work"])
- feedbagService.AssertExpectations(t)
- locateService.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()
- requireSession(handler.SessionManager, handler.GetPresence).ServeHTTP(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()
- requireSession(handler.SessionManager, handler.GetPresence).ServeHTTP(rr, req)
- assert.Equal(t, http.StatusUnauthorized, rr.Code)
- assert.Contains(t, rr.Body.String(), "invalid or expired session")
- }
- 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()
- requireSession(handler.SessionManager, handler.SetState).ServeHTTP(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) {
- oscarInstance := state.NewSession().AddInstance()
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
- 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()
- requireSession(handler.SessionManager, handler.SetState).ServeHTTP(rr, req)
- assert.Equal(t, http.StatusBadRequest, rr.Code)
- assert.Contains(t, rr.Body.String(), "invalid state parameter")
- }
- func TestPresenceHandler_SetState_EmitsMyInfoEvent(t *testing.T) {
- // The AIM client re-renders its own status badge only from "myInfo" events,
- // so setState must queue one on the user's own session for the change to be
- // visible in their UI.
- oscarInstance := state.NewSession().AddInstance()
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
- broadcaster := &MockBuddyBroadcaster{}
- broadcaster.On("BroadcastBuddyArrived", mock.Anything, mock.Anything, mock.Anything).Return(nil)
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- BuddyBroadcaster: broadcaster,
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/setState?aimsid="+aimsid+"&state=away&awayMsg=brb", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- requireSession(handler.SessionManager, handler.SetState).ServeHTTP(rr, req)
- assert.Equal(t, http.StatusOK, rr.Code)
- session, err := sessionMgr.GetSession(context.Background(), aimsid)
- assert.NoError(t, err)
- var myInfo map[string]interface{}
- for _, event := range session.EventQueue.GetAllEvents() {
- if event.Type == "myInfo" {
- myInfo, _ = event.Data.(map[string]interface{})
- }
- }
- assert.NotNil(t, myInfo, "expected a myInfo event to be queued")
- assert.Equal(t, "away", myInfo["state"])
- assert.Equal(t, "brb", myInfo["awayMsg"])
- assert.Equal(t, "testuser", myInfo["aimId"])
- }
- func TestPresenceHandler_SetState_MyInfoNormalizesAimID(t *testing.T) {
- // The client shallow-merges myInfo onto the shared user object, so aimId must
- // be the normalized id while displayId and friendly keep the user's own
- // casing and spacing.
- oscarInstance := state.NewSession().AddInstance()
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("Mike Kelly", oscarInstance)
- broadcaster := &MockBuddyBroadcaster{}
- broadcaster.On("BroadcastBuddyArrived", mock.Anything, mock.Anything, mock.Anything).Return(nil)
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- BuddyBroadcaster: broadcaster,
- Logger: slog.Default(),
- }
- req, err := http.NewRequest("GET", "/presence/setState?aimsid="+aimsid+"&state=away", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- requireSession(handler.SessionManager, handler.SetState).ServeHTTP(rr, req)
- assert.Equal(t, http.StatusOK, rr.Code)
- // The setState response body carries the same identity fields.
- var resp struct {
- Response struct {
- Data map[string]interface{} `json:"data"`
- } `json:"response"`
- }
- assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
- assert.Equal(t, "mikekelly", resp.Response.Data["aimId"])
- assert.Equal(t, "Mike Kelly", resp.Response.Data["displayId"])
- session, err := sessionMgr.GetSession(context.Background(), aimsid)
- assert.NoError(t, err)
- var myInfo map[string]interface{}
- for _, event := range session.EventQueue.GetAllEvents() {
- if event.Type == "myInfo" {
- myInfo, _ = event.Data.(map[string]interface{})
- }
- }
- require.NotNil(t, myInfo, "expected a myInfo event to be queued")
- assert.Equal(t, "mikekelly", myInfo["aimId"])
- assert.Equal(t, "Mike Kelly", myInfo["displayId"])
- assert.Equal(t, "Mike Kelly", myInfo["friendly"])
- }
- func TestPresenceHandler_SetState_NoOSCARSession_Rejected(t *testing.T) {
- // A nil OSCARSession is a broken server invariant (guests are unsupported),
- // so the session middleware rejects it with a 500 before the handler runs.
- 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()
- requireSession(handler.SessionManager, handler.SetState).ServeHTTP(rr, req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- assert.Contains(t, rr.Body.String(), "internal server error")
- }
- 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
- expectedStatusCode int
- checkRedirect func(*testing.T, *httptest.ResponseRecorder)
- }{
- {
- name: "Redirect_OfflineUser",
- // No aimsid, so there is no OSCAR session to query on behalf of and
- // the target resolves to offline.
- queryParams: "name=offlineuser",
- 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: "",
- expectedStatusCode: http.StatusBadRequest,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- handler := &PresenceHandler{
- SessionManager: state.NewWebAPISessionManager(),
- LocateService: &MockLocateService{},
- Logger: slog.Default(),
- }
- 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)
- }
- })
- }
- }
- func TestPresenceHandler_SetProfile(t *testing.T) {
- oscarInstance := state.NewSession().AddInstance()
- tests := []struct {
- name string
- queryParams string
- setupMocks func(*MockLocateService)
- expectedStatusCode int
- checkResponse func(*testing.T, string)
- }{
- {
- name: "Success_SetProfile",
- queryParams: "profile=Hello+World",
- setupMocks: func(ls *MockLocateService) {
- ls.On("SetInfo", mock.Anything, oscarInstance, mock.AnythingOfType("wire.SNAC_0x02_0x04_LocateSetInfo")).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(ls *MockLocateService) {},
- 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) {
- locateService := &MockLocateService{}
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- LocateService: locateService,
- Logger: slog.Default(),
- }
- tt.setupMocks(locateService)
- reqURL := "/presence/setProfile?aimsid=" + aimsid + "&" + tt.queryParams
- req, err := http.NewRequest("GET", reqURL, nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- requireSession(handler.SessionManager, handler.SetProfile).ServeHTTP(rr, req)
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- if tt.checkResponse != nil {
- responseBody := strings.TrimSpace(rr.Body.String())
- tt.checkResponse(t, responseBody)
- }
- locateService.AssertExpectations(t)
- })
- }
- }
- func TestPresenceHandler_GetProfile(t *testing.T) {
- locateService := &MockLocateService{}
- oscarInstance := state.NewSession().AddInstance()
- sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
- handler := &PresenceHandler{
- SessionManager: sessionMgr,
- LocateService: locateService,
- Logger: slog.Default(),
- }
- locateService.On("UserInfoQuery", mock.Anything, mock.Anything, mock.Anything, screenNameMatcher("testuser")).
- Return(wire.SNACMessage{
- Body: wire.SNAC_0x02_0x06_LocateUserInfoReply{
- LocateInfo: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "My profile"),
- },
- },
- },
- }, nil)
- req, err := http.NewRequest("GET", "/presence/getProfile?aimsid="+aimsid, nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- requireSession(handler.SessionManager, handler.GetProfile).ServeHTTP(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"`)
- locateService.AssertExpectations(t)
- }
|