| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- package handlers
- import (
- "context"
- "errors"
- "log/slog"
- "net/http"
- "net/http/httptest"
- "net/url"
- "strings"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/mk6i/open-oscar-server/server/webapi/types"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // MockWebAPISessionManager is a mock implementation of the WebAPISessionManager
- type MockWebAPISessionManager struct {
- mock.Mock
- }
- func (m *MockWebAPISessionManager) GetSession(ctx context.Context, aimsid string) (*state.WebAPISession, error) {
- args := m.Called(ctx, aimsid)
- if session := args.Get(0); session != nil {
- return session.(*state.WebAPISession), args.Error(1)
- }
- return nil, args.Error(1)
- }
- func (m *MockWebAPISessionManager) TouchSession(ctx context.Context, aimsid string) error {
- args := m.Called(ctx, aimsid)
- return args.Error(0)
- }
- // MockFeedbagManager is a mock implementation of FeedbagManager
- type MockFeedbagManager struct {
- mock.Mock
- }
- func (m *MockFeedbagManager) 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 *MockFeedbagManager) InsertItem(ctx context.Context, screenName state.IdentScreenName, item wire.FeedbagItem) error {
- args := m.Called(ctx, screenName, item)
- return args.Error(0)
- }
- func (m *MockFeedbagManager) UpdateItem(ctx context.Context, screenName state.IdentScreenName, item wire.FeedbagItem) error {
- args := m.Called(ctx, screenName, item)
- return args.Error(0)
- }
- func (m *MockFeedbagManager) DeleteItem(ctx context.Context, screenName state.IdentScreenName, item wire.FeedbagItem) error {
- args := m.Called(ctx, screenName, item)
- return args.Error(0)
- }
- func TestBuddyListHandler_AddTempBuddy(t *testing.T) {
- tests := []struct {
- name string
- queryParams map[string][]string
- setupMocks func(*MockWebAPISessionManager, *MockFeedbagManager, string)
- expectedStatusCode int
- expectedResponse string
- checkSession func(*testing.T, *state.WebAPISession)
- }{
- {
- name: "Success_SingleBuddy",
- queryParams: map[string][]string{
- "aimsid": {"test-session-id"},
- "t": {"buddy1"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- TempBuddies: nil,
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"buddyNames":["buddy1"],"resultCode":"success"}}}`,
- checkSession: func(t *testing.T, session *state.WebAPISession) {
- assert.NotNil(t, session.TempBuddies)
- assert.True(t, session.TempBuddies["buddy1"])
- assert.Equal(t, 1, len(session.TempBuddies))
- },
- },
- {
- name: "Success_MultipleBuddies",
- queryParams: map[string][]string{
- "aimsid": {"test-session-id"},
- "t": {"buddy1", "buddy2", "buddy3"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- TempBuddies: nil,
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"buddyNames":["buddy1","buddy2","buddy3"],"resultCode":"success"}}}`,
- checkSession: func(t *testing.T, session *state.WebAPISession) {
- assert.NotNil(t, session.TempBuddies)
- assert.True(t, session.TempBuddies["buddy1"])
- assert.True(t, session.TempBuddies["buddy2"])
- assert.True(t, session.TempBuddies["buddy3"])
- assert.Equal(t, 3, len(session.TempBuddies))
- },
- },
- {
- name: "Success_AddToExistingTempBuddies",
- queryParams: map[string][]string{
- "aimsid": {"test-session-id"},
- "t": {"buddy2"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- TempBuddies: map[string]bool{
- "buddy1": true,
- },
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"buddyNames":["buddy2"],"resultCode":"success"}}}`,
- checkSession: func(t *testing.T, session *state.WebAPISession) {
- assert.NotNil(t, session.TempBuddies)
- assert.True(t, session.TempBuddies["buddy1"])
- assert.True(t, session.TempBuddies["buddy2"])
- assert.Equal(t, 2, len(session.TempBuddies))
- },
- },
- {
- name: "Error_MissingAimSID",
- queryParams: map[string][]string{
- "t": {"buddy1"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {},
- expectedStatusCode: http.StatusBadRequest,
- expectedResponse: `{"response":{"statusCode":400,"statusText":"missing aimsid parameter"}}`,
- },
- {
- name: "Error_MissingBuddyNames",
- queryParams: map[string][]string{
- "aimsid": {"test-session-id"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- },
- expectedStatusCode: http.StatusBadRequest,
- expectedResponse: `{"response":{"statusCode":400,"statusText":"missing buddy names (t parameter)"}}`,
- },
- {
- name: "Error_SessionNotFound",
- queryParams: map[string][]string{
- "aimsid": {"invalid-session"},
- "t": {"buddy1"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- sm.On("GetSession", mock.Anything, aimsid).Return(nil, state.ErrNoWebAPISession)
- },
- expectedStatusCode: http.StatusNotFound,
- expectedResponse: `{"response":{"statusCode":404,"statusText":"session not found"}}`,
- },
- {
- name: "Error_SessionExpired",
- queryParams: map[string][]string{
- "aimsid": {"expired-session"},
- "t": {"buddy1"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- sm.On("GetSession", mock.Anything, aimsid).Return(nil, state.ErrWebAPISessionExpired)
- },
- expectedStatusCode: http.StatusGone,
- expectedResponse: `{"response":{"statusCode":410,"statusText":"session expired"}}`,
- },
- {
- name: "Error_InternalServerError",
- queryParams: map[string][]string{
- "aimsid": {"test-session-id"},
- "t": {"buddy1"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- sm.On("GetSession", mock.Anything, aimsid).Return(nil, errors.New("database error"))
- },
- expectedStatusCode: http.StatusInternalServerError,
- expectedResponse: `{"response":{"statusCode":500,"statusText":"internal server error"}}`,
- },
- {
- name: "Success_WithWhitespace",
- queryParams: map[string][]string{
- "aimsid": {"test-session-id"},
- "t": {" buddy1 ", "buddy2 ", " buddy3"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- TempBuddies: nil,
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"buddyNames":[" buddy1 ","buddy2 "," buddy3"],"resultCode":"success"}}}`,
- checkSession: func(t *testing.T, session *state.WebAPISession) {
- assert.NotNil(t, session.TempBuddies)
- assert.True(t, session.TempBuddies["buddy1"])
- assert.True(t, session.TempBuddies["buddy2"])
- assert.True(t, session.TempBuddies["buddy3"])
- assert.Equal(t, 3, len(session.TempBuddies))
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Setup
- sessionManager := &MockWebAPISessionManager{}
- feedbagManager := &MockFeedbagManager{}
- logger := slog.Default()
- handler := &BuddyListHandler{
- SessionManager: sessionManager,
- FeedbagManager: feedbagManager,
- Logger: logger,
- }
- aimsid := ""
- if aimsids, ok := tt.queryParams["aimsid"]; ok && len(aimsids) > 0 {
- aimsid = aimsids[0]
- }
- tt.setupMocks(sessionManager, feedbagManager, aimsid)
- // Create request with query parameters
- reqURL := "/aim/addTempBuddy"
- if len(tt.queryParams) > 0 {
- values := url.Values{}
- for key, vals := range tt.queryParams {
- for _, val := range vals {
- values.Add(key, val)
- }
- }
- reqURL += "?" + values.Encode()
- }
- req, err := http.NewRequest("GET", reqURL, nil)
- assert.NoError(t, err)
- // Create response recorder
- rr := httptest.NewRecorder()
- // Execute
- handler.AddTempBuddy(rr, req)
- // Verify status code
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- // Verify response body
- responseBody := strings.TrimSpace(rr.Body.String())
- assert.Equal(t, tt.expectedResponse, responseBody)
- // Check session state if provided
- if tt.checkSession != nil && aimsid != "" {
- // Get the session from mock to verify state
- for _, call := range sessionManager.Calls {
- if call.Method == "GetSession" {
- if session, ok := call.ReturnArguments[0].(*state.WebAPISession); ok {
- tt.checkSession(t, session)
- break
- }
- }
- }
- }
- // Verify all mock expectations were met
- sessionManager.AssertExpectations(t)
- feedbagManager.AssertExpectations(t)
- })
- }
- }
- func TestBuddyListHandler_AddTempBuddy_EventQueueBehavior(t *testing.T) {
- // Test that events are properly added to the event queue
- sessionManager := &MockWebAPISessionManager{}
- feedbagManager := &MockFeedbagManager{}
- logger := slog.Default()
- handler := &BuddyListHandler{
- SessionManager: sessionManager,
- FeedbagManager: feedbagManager,
- Logger: logger,
- }
- eventQueue := types.NewEventQueue(100)
- session := &state.WebAPISession{
- AimSID: "test-session",
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: eventQueue,
- TempBuddies: nil,
- LastAccessed: time.Now(),
- }
- sessionManager.On("GetSession", mock.Anything, "test-session").Return(session, nil)
- sessionManager.On("TouchSession", mock.Anything, "test-session").Return(nil)
- req, err := http.NewRequest("GET", "/aim/addTempBuddy?aimsid=test-session&t=buddy1&t=buddy2", nil)
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- handler.AddTempBuddy(rr, req)
- // Verify that events were added to the queue
- assert.Equal(t, http.StatusOK, rr.Code)
- // Check that the event queue has the correct number of events
- events := eventQueue.GetAllEvents()
- assert.GreaterOrEqual(t, len(events), 2, "Should have at least 2 events for 2 buddies")
- // Verify event content
- for _, event := range events {
- assert.Equal(t, types.EventTypeBuddyList, event.Type)
- eventData, ok := event.Data.(types.BuddyListEvent)
- assert.True(t, ok, "Event data should be BuddyListEvent")
- assert.Equal(t, "addTemp", eventData.Action)
- assert.NotNil(t, eventData.Buddy)
- buddyInfo, ok := eventData.Buddy.(*BuddyPresenceInfo)
- assert.True(t, ok, "Buddy should be *BuddyPresenceInfo")
- if ok {
- assert.Contains(t, []string{"buddy1", "buddy2"}, buddyInfo.AimID)
- assert.Equal(t, "offline", buddyInfo.State)
- assert.Equal(t, "aim", buddyInfo.UserType)
- }
- }
- sessionManager.AssertExpectations(t)
- }
- func TestBuddyListHandler_AddBuddy(t *testing.T) {
- tests := []struct {
- name string
- queryParams map[string][]string
- setupMocks func(*MockWebAPISessionManager, *MockFeedbagManager, string)
- expectedStatusCode int
- expectedResponse string
- }{
- {
- name: "Success_AddBuddyToExistingGroup",
- queryParams: map[string][]string{
- "aimsid": {"test-session"},
- "buddy": {"newbuddy"},
- "group": {"Friends"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- // Mock feedbag retrieval with existing group
- existingItems := []wire.FeedbagItem{
- {
- ItemID: 1,
- ClassID: wire.FeedbagClassIdGroup,
- Name: "Friends",
- GroupID: 0,
- },
- }
- fm.On("RetrieveFeedbag", mock.Anything, state.NewIdentScreenName("testuser")).
- Return(existingItems, nil)
- // Mock buddy insertion
- fm.On("InsertItem", mock.Anything, state.NewIdentScreenName("testuser"), mock.MatchedBy(func(item wire.FeedbagItem) bool {
- return item.ClassID == wire.FeedbagClassIdBuddy &&
- item.Name == "newbuddy" &&
- item.GroupID == 1
- })).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"buddyInfo":{"aimId":"newbuddy","state":"offline","userType":"aim"},"resultCode":"success"}}}`,
- },
- {
- name: "Success_AddBuddyCreateNewGroup",
- queryParams: map[string][]string{
- "aimsid": {"test-session"},
- "buddy": {"newbuddy"},
- "group": {"NewGroup"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- // Mock feedbag retrieval with no existing groups
- fm.On("RetrieveFeedbag", mock.Anything, state.NewIdentScreenName("testuser")).
- Return([]wire.FeedbagItem{}, nil)
- // Mock group creation
- fm.On("InsertItem", mock.Anything, state.NewIdentScreenName("testuser"), mock.MatchedBy(func(item wire.FeedbagItem) bool {
- return item.ClassID == wire.FeedbagClassIdGroup &&
- item.Name == "NewGroup" &&
- item.ItemID == 1
- })).Return(nil)
- // Mock buddy insertion
- fm.On("InsertItem", mock.Anything, state.NewIdentScreenName("testuser"), mock.MatchedBy(func(item wire.FeedbagItem) bool {
- return item.ClassID == wire.FeedbagClassIdBuddy &&
- item.Name == "newbuddy" &&
- item.GroupID == 1
- })).Return(nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"buddyInfo":{"aimId":"newbuddy","state":"offline","userType":"aim"},"resultCode":"success"}}}`,
- },
- {
- name: "Error_BuddyAlreadyExists",
- queryParams: map[string][]string{
- "aimsid": {"test-session"},
- "buddy": {"existingbuddy"},
- "group": {"Friends"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- // Mock feedbag retrieval with existing buddy
- existingItems := []wire.FeedbagItem{
- {
- ItemID: 1,
- ClassID: wire.FeedbagClassIdBuddy,
- Name: "existingbuddy",
- GroupID: 1,
- },
- }
- fm.On("RetrieveFeedbag", mock.Anything, state.NewIdentScreenName("testuser")).
- Return(existingItems, nil)
- },
- expectedStatusCode: http.StatusOK,
- expectedResponse: `{"response":{"statusCode":200,"statusText":"OK","data":{"resultCode":"alreadyExists"}}}`,
- },
- {
- name: "Error_MissingBuddyParameter",
- queryParams: map[string][]string{
- "aimsid": {"test-session"},
- "group": {"Friends"},
- },
- setupMocks: func(sm *MockWebAPISessionManager, fm *MockFeedbagManager, aimsid string) {
- session := &state.WebAPISession{
- AimSID: aimsid,
- ScreenName: state.DisplayScreenName("testuser"),
- EventQueue: types.NewEventQueue(100),
- LastAccessed: time.Now(),
- }
- sm.On("GetSession", mock.Anything, aimsid).Return(session, nil)
- sm.On("TouchSession", mock.Anything, aimsid).Return(nil)
- },
- expectedStatusCode: http.StatusBadRequest,
- expectedResponse: `{"response":{"statusCode":400,"statusText":"missing buddy parameter"}}`,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Setup
- sessionManager := &MockWebAPISessionManager{}
- feedbagManager := &MockFeedbagManager{}
- logger := slog.Default()
- handler := &BuddyListHandler{
- SessionManager: sessionManager,
- FeedbagManager: feedbagManager,
- Logger: logger,
- }
- aimsid := ""
- if aimsids, ok := tt.queryParams["aimsid"]; ok && len(aimsids) > 0 {
- aimsid = aimsids[0]
- }
- tt.setupMocks(sessionManager, feedbagManager, aimsid)
- // Create request
- reqURL := "/buddylist/addBuddy"
- if len(tt.queryParams) > 0 {
- values := url.Values{}
- for key, vals := range tt.queryParams {
- for _, val := range vals {
- values.Add(key, val)
- }
- }
- reqURL += "?" + values.Encode()
- }
- req, err := http.NewRequest("GET", reqURL, nil)
- assert.NoError(t, err)
- // Create response recorder
- rr := httptest.NewRecorder()
- // Execute
- handler.AddBuddy(rr, req)
- // Verify
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- responseBody := strings.TrimSpace(rr.Body.String())
- assert.Equal(t, tt.expectedResponse, responseBody)
- sessionManager.AssertExpectations(t)
- feedbagManager.AssertExpectations(t)
- })
- }
- }
|