Просмотр исходного кода

add test coverage to management api

Mike 2 лет назад
Родитель
Сommit
3cba911c2e

+ 3 - 0
.mockery.yaml

@@ -32,6 +32,9 @@ packages:
       OServiceChatHandler:
         config:
           filename: "oservice_chat_mock_test.go"
+      UserManager:
+        config:
+          filename: "user_manager_mock_test.go"
   github.com/mkaminski/goaim/handler:
     interfaces:
       FeedbagManager:

+ 1 - 1
handler/auth_test.go

@@ -848,7 +848,7 @@ func TestAuthService_Signout(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			for _, params := range tt.mockParams.interestedUsersParams {
 				feedbagManager.EXPECT().
-					InterestedUsers(params.screenName).
+					AdjacentUsers(params.screenName).
 					Return(params.users, params.err)
 			}
 			sessionManager := newMockSessionManager(t)

+ 102 - 102
handler/feedbag_manager_mock_test.go

@@ -24,6 +24,64 @@ func (_m *mockFeedbagManager) EXPECT() *mockFeedbagManager_Expecter {
 	return &mockFeedbagManager_Expecter{mock: &_m.Mock}
 }
 
+// AdjacentUsers provides a mock function with given fields: screenName
+func (_m *mockFeedbagManager) AdjacentUsers(screenName string) ([]string, error) {
+	ret := _m.Called(screenName)
+
+	if len(ret) == 0 {
+		panic("no return value specified for AdjacentUsers")
+	}
+
+	var r0 []string
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok {
+		return rf(screenName)
+	}
+	if rf, ok := ret.Get(0).(func(string) []string); ok {
+		r0 = rf(screenName)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]string)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(screenName)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// mockFeedbagManager_AdjacentUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AdjacentUsers'
+type mockFeedbagManager_AdjacentUsers_Call struct {
+	*mock.Call
+}
+
+// AdjacentUsers is a helper method to define mock.On call
+//   - screenName string
+func (_e *mockFeedbagManager_Expecter) AdjacentUsers(screenName interface{}) *mockFeedbagManager_AdjacentUsers_Call {
+	return &mockFeedbagManager_AdjacentUsers_Call{Call: _e.mock.On("AdjacentUsers", screenName)}
+}
+
+func (_c *mockFeedbagManager_AdjacentUsers_Call) Run(run func(screenName string)) *mockFeedbagManager_AdjacentUsers_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(string))
+	})
+	return _c
+}
+
+func (_c *mockFeedbagManager_AdjacentUsers_Call) Return(_a0 []string, _a1 error) *mockFeedbagManager_AdjacentUsers_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *mockFeedbagManager_AdjacentUsers_Call) RunAndReturn(run func(string) ([]string, error)) *mockFeedbagManager_AdjacentUsers_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // BlockedState provides a mock function with given fields: screenName1, screenName2
 func (_m *mockFeedbagManager) BlockedState(screenName1 string, screenName2 string) (state.BlockedState, error) {
 	ret := _m.Called(screenName1, screenName2)
@@ -139,53 +197,6 @@ func (_c *mockFeedbagManager_Buddies_Call) RunAndReturn(run func(string) ([]stri
 	return _c
 }
 
-// DeleteUser provides a mock function with given fields: screenName, items
-func (_m *mockFeedbagManager) FeedbagDelete(screenName string, items []oscar.FeedbagItem) error {
-	ret := _m.Called(screenName, items)
-
-	if len(ret) == 0 {
-		panic("no return value specified for FeedbagDelete")
-	}
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(string, []oscar.FeedbagItem) error); ok {
-		r0 = rf(screenName, items)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// mockFeedbagManager_DeleteUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeedbagDelete'
-type mockFeedbagManager_DeleteUser_Call struct {
-	*mock.Call
-}
-
-// DeleteUser is a helper method to define mock.On call
-//   - screenName string
-//   - items []oscar.FeedbagItem
-func (_e *mockFeedbagManager_Expecter) DeleteUser(screenName interface{}, items interface{}) *mockFeedbagManager_DeleteUser_Call {
-	return &mockFeedbagManager_DeleteUser_Call{Call: _e.mock.On("FeedbagDelete", screenName, items)}
-}
-
-func (_c *mockFeedbagManager_DeleteUser_Call) Run(run func(screenName string, items []oscar.FeedbagItem)) *mockFeedbagManager_DeleteUser_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string), args[1].([]oscar.FeedbagItem))
-	})
-	return _c
-}
-
-func (_c *mockFeedbagManager_DeleteUser_Call) Return(_a0 error) *mockFeedbagManager_DeleteUser_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *mockFeedbagManager_DeleteUser_Call) RunAndReturn(run func(string, []oscar.FeedbagItem) error) *mockFeedbagManager_DeleteUser_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // Feedbag provides a mock function with given fields: screenName
 func (_m *mockFeedbagManager) Feedbag(screenName string) ([]oscar.FeedbagItem, error) {
 	ret := _m.Called(screenName)
@@ -244,81 +255,70 @@ func (_c *mockFeedbagManager_Feedbag_Call) RunAndReturn(run func(string) ([]osca
 	return _c
 }
 
-// FeedbagLastModified provides a mock function with given fields: screenName
-func (_m *mockFeedbagManager) FeedbagLastModified(screenName string) (time.Time, error) {
-	ret := _m.Called(screenName)
+// FeedbagDelete provides a mock function with given fields: screenName, items
+func (_m *mockFeedbagManager) FeedbagDelete(screenName string, items []oscar.FeedbagItem) error {
+	ret := _m.Called(screenName, items)
 
 	if len(ret) == 0 {
-		panic("no return value specified for FeedbagLastModified")
-	}
-
-	var r0 time.Time
-	var r1 error
-	if rf, ok := ret.Get(0).(func(string) (time.Time, error)); ok {
-		return rf(screenName)
-	}
-	if rf, ok := ret.Get(0).(func(string) time.Time); ok {
-		r0 = rf(screenName)
-	} else {
-		r0 = ret.Get(0).(time.Time)
+		panic("no return value specified for FeedbagDelete")
 	}
 
-	if rf, ok := ret.Get(1).(func(string) error); ok {
-		r1 = rf(screenName)
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, []oscar.FeedbagItem) error); ok {
+		r0 = rf(screenName, items)
 	} else {
-		r1 = ret.Error(1)
+		r0 = ret.Error(0)
 	}
 
-	return r0, r1
+	return r0
 }
 
-// mockFeedbagManager_FeedbagLastModified_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeedbagLastModified'
-type mockFeedbagManager_FeedbagLastModified_Call struct {
+// mockFeedbagManager_FeedbagDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeedbagDelete'
+type mockFeedbagManager_FeedbagDelete_Call struct {
 	*mock.Call
 }
 
-// FeedbagLastModified is a helper method to define mock.On call
+// FeedbagDelete is a helper method to define mock.On call
 //   - screenName string
-func (_e *mockFeedbagManager_Expecter) FeedbagLastModified(screenName interface{}) *mockFeedbagManager_FeedbagLastModified_Call {
-	return &mockFeedbagManager_FeedbagLastModified_Call{Call: _e.mock.On("FeedbagLastModified", screenName)}
+//   - items []oscar.FeedbagItem
+func (_e *mockFeedbagManager_Expecter) FeedbagDelete(screenName interface{}, items interface{}) *mockFeedbagManager_FeedbagDelete_Call {
+	return &mockFeedbagManager_FeedbagDelete_Call{Call: _e.mock.On("FeedbagDelete", screenName, items)}
 }
 
-func (_c *mockFeedbagManager_FeedbagLastModified_Call) Run(run func(screenName string)) *mockFeedbagManager_FeedbagLastModified_Call {
+func (_c *mockFeedbagManager_FeedbagDelete_Call) Run(run func(screenName string, items []oscar.FeedbagItem)) *mockFeedbagManager_FeedbagDelete_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string))
+		run(args[0].(string), args[1].([]oscar.FeedbagItem))
 	})
 	return _c
 }
 
-func (_c *mockFeedbagManager_FeedbagLastModified_Call) Return(_a0 time.Time, _a1 error) *mockFeedbagManager_FeedbagLastModified_Call {
-	_c.Call.Return(_a0, _a1)
+func (_c *mockFeedbagManager_FeedbagDelete_Call) Return(_a0 error) *mockFeedbagManager_FeedbagDelete_Call {
+	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *mockFeedbagManager_FeedbagLastModified_Call) RunAndReturn(run func(string) (time.Time, error)) *mockFeedbagManager_FeedbagLastModified_Call {
+func (_c *mockFeedbagManager_FeedbagDelete_Call) RunAndReturn(run func(string, []oscar.FeedbagItem) error) *mockFeedbagManager_FeedbagDelete_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
-// InterestedUsers provides a mock function with given fields: screenName
-func (_m *mockFeedbagManager) AdjacentUsers(screenName string) ([]string, error) {
+// FeedbagLastModified provides a mock function with given fields: screenName
+func (_m *mockFeedbagManager) FeedbagLastModified(screenName string) (time.Time, error) {
 	ret := _m.Called(screenName)
 
 	if len(ret) == 0 {
-		panic("no return value specified for AdjacentUsers")
+		panic("no return value specified for FeedbagLastModified")
 	}
 
-	var r0 []string
+	var r0 time.Time
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok {
+	if rf, ok := ret.Get(0).(func(string) (time.Time, error)); ok {
 		return rf(screenName)
 	}
-	if rf, ok := ret.Get(0).(func(string) []string); ok {
+	if rf, ok := ret.Get(0).(func(string) time.Time); ok {
 		r0 = rf(screenName)
 	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]string)
-		}
+		r0 = ret.Get(0).(time.Time)
 	}
 
 	if rf, ok := ret.Get(1).(func(string) error); ok {
@@ -330,35 +330,35 @@ func (_m *mockFeedbagManager) AdjacentUsers(screenName string) ([]string, error)
 	return r0, r1
 }
 
-// mockFeedbagManager_InterestedUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AdjacentUsers'
-type mockFeedbagManager_InterestedUsers_Call struct {
+// mockFeedbagManager_FeedbagLastModified_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeedbagLastModified'
+type mockFeedbagManager_FeedbagLastModified_Call struct {
 	*mock.Call
 }
 
-// InterestedUsers is a helper method to define mock.On call
+// FeedbagLastModified is a helper method to define mock.On call
 //   - screenName string
-func (_e *mockFeedbagManager_Expecter) InterestedUsers(screenName interface{}) *mockFeedbagManager_InterestedUsers_Call {
-	return &mockFeedbagManager_InterestedUsers_Call{Call: _e.mock.On("AdjacentUsers", screenName)}
+func (_e *mockFeedbagManager_Expecter) FeedbagLastModified(screenName interface{}) *mockFeedbagManager_FeedbagLastModified_Call {
+	return &mockFeedbagManager_FeedbagLastModified_Call{Call: _e.mock.On("FeedbagLastModified", screenName)}
 }
 
-func (_c *mockFeedbagManager_InterestedUsers_Call) Run(run func(screenName string)) *mockFeedbagManager_InterestedUsers_Call {
+func (_c *mockFeedbagManager_FeedbagLastModified_Call) Run(run func(screenName string)) *mockFeedbagManager_FeedbagLastModified_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		run(args[0].(string))
 	})
 	return _c
 }
 
-func (_c *mockFeedbagManager_InterestedUsers_Call) Return(_a0 []string, _a1 error) *mockFeedbagManager_InterestedUsers_Call {
+func (_c *mockFeedbagManager_FeedbagLastModified_Call) Return(_a0 time.Time, _a1 error) *mockFeedbagManager_FeedbagLastModified_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *mockFeedbagManager_InterestedUsers_Call) RunAndReturn(run func(string) ([]string, error)) *mockFeedbagManager_InterestedUsers_Call {
+func (_c *mockFeedbagManager_FeedbagLastModified_Call) RunAndReturn(run func(string) (time.Time, error)) *mockFeedbagManager_FeedbagLastModified_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
-// UpsertFeedbag provides a mock function with given fields: screenName, items
+// FeedbagUpsert provides a mock function with given fields: screenName, items
 func (_m *mockFeedbagManager) FeedbagUpsert(screenName string, items []oscar.FeedbagItem) error {
 	ret := _m.Called(screenName, items)
 
@@ -376,31 +376,31 @@ func (_m *mockFeedbagManager) FeedbagUpsert(screenName string, items []oscar.Fee
 	return r0
 }
 
-// mockFeedbagManager_UpsertFeedbag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeedbagUpsert'
-type mockFeedbagManager_UpsertFeedbag_Call struct {
+// mockFeedbagManager_FeedbagUpsert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeedbagUpsert'
+type mockFeedbagManager_FeedbagUpsert_Call struct {
 	*mock.Call
 }
 
-// UpsertFeedbag is a helper method to define mock.On call
+// FeedbagUpsert is a helper method to define mock.On call
 //   - screenName string
 //   - items []oscar.FeedbagItem
-func (_e *mockFeedbagManager_Expecter) UpsertFeedbag(screenName interface{}, items interface{}) *mockFeedbagManager_UpsertFeedbag_Call {
-	return &mockFeedbagManager_UpsertFeedbag_Call{Call: _e.mock.On("FeedbagUpsert", screenName, items)}
+func (_e *mockFeedbagManager_Expecter) FeedbagUpsert(screenName interface{}, items interface{}) *mockFeedbagManager_FeedbagUpsert_Call {
+	return &mockFeedbagManager_FeedbagUpsert_Call{Call: _e.mock.On("FeedbagUpsert", screenName, items)}
 }
 
-func (_c *mockFeedbagManager_UpsertFeedbag_Call) Run(run func(screenName string, items []oscar.FeedbagItem)) *mockFeedbagManager_UpsertFeedbag_Call {
+func (_c *mockFeedbagManager_FeedbagUpsert_Call) Run(run func(screenName string, items []oscar.FeedbagItem)) *mockFeedbagManager_FeedbagUpsert_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		run(args[0].(string), args[1].([]oscar.FeedbagItem))
 	})
 	return _c
 }
 
-func (_c *mockFeedbagManager_UpsertFeedbag_Call) Return(_a0 error) *mockFeedbagManager_UpsertFeedbag_Call {
+func (_c *mockFeedbagManager_FeedbagUpsert_Call) Return(_a0 error) *mockFeedbagManager_FeedbagUpsert_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *mockFeedbagManager_UpsertFeedbag_Call) RunAndReturn(run func(string, []oscar.FeedbagItem) error) *mockFeedbagManager_UpsertFeedbag_Call {
+func (_c *mockFeedbagManager_FeedbagUpsert_Call) RunAndReturn(run func(string, []oscar.FeedbagItem) error) *mockFeedbagManager_FeedbagUpsert_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 3 - 3
handler/feedbag_test.go

@@ -761,7 +761,7 @@ func TestFeedbagService_InsertItemHandler(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			for _, params := range tc.mockParams.feedbagManagerParams.feedbagUpsertParams {
 				feedbagManager.EXPECT().
-					UpsertFeedbag(params.screenName, params.items).
+					FeedbagUpsert(params.screenName, params.items).
 					Return(nil)
 			}
 			messageRelayer := newMockMessageRelayer(t)
@@ -996,7 +996,7 @@ func TestFeedbagService_UpdateItemHandler(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			for _, params := range tc.mockParams.feedbagManagerParams.feedbagUpsertParams {
 				feedbagManager.EXPECT().
-					UpsertFeedbag(params.screenName, params.items).
+					FeedbagUpsert(params.screenName, params.items).
 					Return(nil)
 			}
 			messageRelayer := newMockMessageRelayer(t)
@@ -1311,7 +1311,7 @@ func TestFeedbagService_DeleteItemHandler(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			for _, params := range tc.mockParams.feedbagManagerParams.feedbagDeleteParams {
 				feedbagManager.EXPECT().
-					DeleteUser(params.screenName, params.items).
+					FeedbagDelete(params.screenName, params.items).
 					Return(nil)
 			}
 			messageRelayer := newMockMessageRelayer(t)

+ 1 - 1
handler/icbm_test.go

@@ -550,7 +550,7 @@ func TestICBMService_EvilRequestHandler(t *testing.T) {
 				Return(tc.blockedState, nil).
 				Maybe()
 			feedbagManager.EXPECT().
-				InterestedUsers(tc.recipientScreenName).
+				AdjacentUsers(tc.recipientScreenName).
 				Return(tc.recipientBuddies, nil).
 				Maybe()
 			recipSess := newTestSession(tc.recipientScreenName, sessOptCannedSignonTime)

+ 2 - 2
handler/locate_test.go

@@ -467,13 +467,13 @@ func TestLocateService_SetInfoHandler(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			for _, params := range tt.mockParams.interestedUsersParams {
 				feedbagManager.EXPECT().
-					InterestedUsers(params.screenName).
+					AdjacentUsers(params.screenName).
 					Return(params.users, nil)
 			}
 			profileManager := newMockProfileManager(t)
 			if msg, hasProf := tt.inBody.String(oscar.LocateTLVTagsInfoSigData); hasProf {
 				profileManager.EXPECT().
-					UpsertProfile(tt.userSession.ScreenName(), msg).
+					SetProfile(tt.userSession.ScreenName(), msg).
 					Return(nil)
 			}
 			svc := NewLocateService(messageRelayer, feedbagManager, profileManager)

+ 3 - 3
handler/oservice_test.go

@@ -289,7 +289,7 @@ func TestSetUserInfoFieldsHandler(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			for user, friends := range tc.interestedUserLookups {
 				feedbagManager.EXPECT().
-					InterestedUsers(user).
+					AdjacentUsers(user).
 					Return(friends, nil).
 					Maybe()
 			}
@@ -651,7 +651,7 @@ func TestOServiceService_IdleNotificationHandler(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
 			feedbagManager.EXPECT().
-				InterestedUsers(tt.recipientScreenName).
+				AdjacentUsers(tt.recipientScreenName).
 				Return(tt.recipientBuddies, nil).
 				Maybe()
 			messageRelayer := newMockMessageRelayer(t)
@@ -774,7 +774,7 @@ func TestOServiceServiceForBOS_ClientOnlineHandler(t *testing.T) {
 			messageRelayer := newMockMessageRelayer(t)
 			for _, params := range tt.interestedUsersParams {
 				feedbagManager.EXPECT().
-					InterestedUsers(params.screenName).
+					AdjacentUsers(params.screenName).
 					Return(params.users, nil)
 			}
 			for _, params := range tt.broadcastToScreenNamesParams {

+ 9 - 9
handler/profile_manager_mock_test.go

@@ -73,7 +73,7 @@ func (_c *mockProfileManager_Profile_Call) RunAndReturn(run func(string) (string
 	return _c
 }
 
-// UpsertProfile provides a mock function with given fields: screenName, body
+// SetProfile provides a mock function with given fields: screenName, body
 func (_m *mockProfileManager) SetProfile(screenName string, body string) error {
 	ret := _m.Called(screenName, body)
 
@@ -91,31 +91,31 @@ func (_m *mockProfileManager) SetProfile(screenName string, body string) error {
 	return r0
 }
 
-// mockProfileManager_UpsertProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetProfile'
-type mockProfileManager_UpsertProfile_Call struct {
+// mockProfileManager_SetProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetProfile'
+type mockProfileManager_SetProfile_Call struct {
 	*mock.Call
 }
 
-// UpsertProfile is a helper method to define mock.On call
+// SetProfile is a helper method to define mock.On call
 //   - screenName string
 //   - body string
-func (_e *mockProfileManager_Expecter) UpsertProfile(screenName interface{}, body interface{}) *mockProfileManager_UpsertProfile_Call {
-	return &mockProfileManager_UpsertProfile_Call{Call: _e.mock.On("SetProfile", screenName, body)}
+func (_e *mockProfileManager_Expecter) SetProfile(screenName interface{}, body interface{}) *mockProfileManager_SetProfile_Call {
+	return &mockProfileManager_SetProfile_Call{Call: _e.mock.On("SetProfile", screenName, body)}
 }
 
-func (_c *mockProfileManager_UpsertProfile_Call) Run(run func(screenName string, body string)) *mockProfileManager_UpsertProfile_Call {
+func (_c *mockProfileManager_SetProfile_Call) Run(run func(screenName string, body string)) *mockProfileManager_SetProfile_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		run(args[0].(string), args[1].(string))
 	})
 	return _c
 }
 
-func (_c *mockProfileManager_UpsertProfile_Call) Return(_a0 error) *mockProfileManager_UpsertProfile_Call {
+func (_c *mockProfileManager_SetProfile_Call) Return(_a0 error) *mockProfileManager_SetProfile_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *mockProfileManager_UpsertProfile_Call) RunAndReturn(run func(string, string) error) *mockProfileManager_UpsertProfile_Call {
+func (_c *mockProfileManager_SetProfile_Call) RunAndReturn(run func(string, string) error) *mockProfileManager_SetProfile_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 1 - 1
handler/test_helpers.go

@@ -101,7 +101,7 @@ type blockedStateParams []struct {
 }
 
 // interestedUsersParams is the list of parameters passed at the mock
-// FeedbagManager.InterestedUsers call site
+// FeedbagManager.AdjacentUsers call site
 type interestedUsersParams []struct {
 	screenName string
 	users      []string

+ 39 - 25
server/mgmt_api.go

@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"log/slog"
-	"net"
 	"net/http"
 	"os"
 
@@ -12,37 +11,51 @@ import (
 	"github.com/mkaminski/goaim/state"
 )
 
-func StartManagementAPI(fs *state.SQLiteUserStore, logger *slog.Logger) {
-	http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
-		switch r.Method {
-		case http.MethodGet:
-			getUsers(fs, w, r)
-		case http.MethodPost:
-			createUser(fs, w, r)
-		default:
-			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
-		}
-	})
+type UserManager interface {
+	AllUsers() ([]state.User, error)
+	InsertUser(u state.User) error
+}
+
+func StartManagementAPI(userManager UserManager, logger *slog.Logger) {
+	uh := userHandler{
+		UserManager: userManager,
+		logger:      logger,
+	}
+	mux := http.NewServeMux()
+	mux.HandleFunc("/user", uh.ServeHTTP)
+
 	//todo make port configurable
 	addr := Address("", 8080)
-	listener, err := net.Listen("tcp", addr)
-	if err != nil {
+	logger.Info("starting management API server", "addr", addr)
+	if err := http.ListenAndServe(addr, mux); err != nil {
 		logger.Error("unable to bind management API address address", "err", err.Error())
 		os.Exit(1)
 	}
-	logger.Info("starting management API server", "addr", addr)
-	if err := http.Serve(listener, nil); err != nil {
-		logger.Info("unable to start management API server", "err", err.Error())
-		os.Exit(1)
+}
+
+type userHandler struct {
+	UserManager
+	logger *slog.Logger
+}
+
+func (uh userHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	switch r.Method {
+	case http.MethodGet:
+		uh.getUsers(w, r)
+	case http.MethodPost:
+		uh.createUser(w, r)
+	default:
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
 	}
 }
 
 // getUsers handles the GET /user endpoint.
-func getUsers(fs *state.SQLiteUserStore, w http.ResponseWriter, r *http.Request) {
+func (uh userHandler) getUsers(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	users, err := fs.AllUsers()
+	users, err := uh.AllUsers()
 	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
+		uh.logger.Error("error in GET /user", "err", err.Error())
+		http.Error(w, "internal server error", http.StatusInternalServerError)
 		return
 	}
 	if err := json.NewEncoder(w).Encode(users); err != nil {
@@ -57,17 +70,18 @@ type CreateUser struct {
 }
 
 // createUser handles the POST /user endpoint.
-func createUser(fs *state.SQLiteUserStore, w http.ResponseWriter, r *http.Request) {
+func (uh userHandler) createUser(w http.ResponseWriter, r *http.Request) {
 	var newUser CreateUser
 	if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
+		http.Error(w, "malformed input", http.StatusBadRequest)
 		return
 	}
 	newUser.AuthKey = uuid.New().String()
 	// todo does the request contain authkey?
 	newUser.HashPassword(newUser.Password)
-	if err := fs.InsertUser(newUser.User); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
+	if err := uh.InsertUser(newUser.User); err != nil {
+		uh.logger.Error("error in GET /user", "err", err.Error())
+		http.Error(w, "internal server error", http.StatusInternalServerError)
 		return
 	}
 

+ 157 - 0
server/mgmt_api_test.go

@@ -0,0 +1,157 @@
+package server
+
+import (
+	"io"
+	"log/slog"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"github.com/mkaminski/goaim/state"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestUserHandler_GET(t *testing.T) {
+	tt := []struct {
+		name           string
+		users          []state.User
+		userHandlerErr error
+		want           string
+		statusCode     int
+	}{
+		{
+			name:       "without users",
+			users:      []state.User{},
+			want:       `[]`,
+			statusCode: http.StatusOK,
+		},
+		{
+			name: "with users",
+			users: []state.User{
+				{ScreenName: "userA"},
+				{ScreenName: "userB"},
+			},
+			want:       `[{"screen_name":"userA"},{"screen_name":"userB"}]`,
+			statusCode: http.StatusOK,
+		},
+		{
+			name:           "user handler error",
+			users:          []state.User{},
+			userHandlerErr: io.EOF,
+			want:           `internal server error`,
+			statusCode:     http.StatusInternalServerError,
+		},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			request := httptest.NewRequest(http.MethodGet, "/users", nil)
+			responseRecorder := httptest.NewRecorder()
+
+			userManager := newMockUserManager(t)
+			userManager.EXPECT().
+				AllUsers().
+				Return(tc.users, tc.userHandlerErr)
+
+			userHandler := userHandler{
+				UserManager: userManager,
+				logger:      slog.Default(),
+			}
+			userHandler.ServeHTTP(responseRecorder, request)
+
+			if responseRecorder.Code != tc.statusCode {
+				t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
+			}
+
+			if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
+				t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
+			}
+		})
+	}
+}
+
+func TestUserHandler_POST(t *testing.T) {
+	tt := []struct {
+		name           string
+		body           string
+		user           state.User
+		userHandlerErr error
+		want           string
+		statusCode     int
+	}{
+		{
+			name: "with valid user",
+			body: `{"screen_name":"userA", "password":"thepassword"}`,
+			user: state.User{
+				ScreenName: "userA",
+			},
+			want:       `User account created successfully.`,
+			statusCode: http.StatusCreated,
+		},
+		{
+			name: "with malformed body",
+			body: `{"screen_name":"userA", "password":"thepassword"`,
+			user: state.User{
+				ScreenName: "userA",
+			},
+			want:       `malformed input`,
+			statusCode: http.StatusBadRequest,
+		},
+		{
+			name: "user handler error",
+			body: `{"screen_name":"userA", "password":"thepassword"}`,
+			user: state.User{
+				ScreenName: "userA",
+			},
+			userHandlerErr: io.EOF,
+			want:           `internal server error`,
+			statusCode:     http.StatusInternalServerError,
+		},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			request := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(tc.body))
+			responseRecorder := httptest.NewRecorder()
+
+			userManager := newMockUserManager(t)
+			userManager.EXPECT().
+				InsertUser(mock.Anything).
+				Return(tc.userHandlerErr).
+				Maybe()
+
+			userHandler := userHandler{
+				UserManager: userManager,
+				logger:      slog.Default(),
+			}
+			userHandler.ServeHTTP(responseRecorder, request)
+
+			if responseRecorder.Code != tc.statusCode {
+				t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
+			}
+
+			if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
+				t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
+			}
+		})
+	}
+}
+
+func TestUserHandler_DisallowedMethod(t *testing.T) {
+	request := httptest.NewRequest(http.MethodPut, "/users", nil)
+	responseRecorder := httptest.NewRecorder()
+
+	userHandler := userHandler{}
+	userHandler.ServeHTTP(responseRecorder, request)
+
+	wantCode := http.StatusMethodNotAllowed
+	if responseRecorder.Code != wantCode {
+		t.Errorf("want status '%d', got '%d'", http.StatusMethodNotAllowed, responseRecorder.Code)
+	}
+
+	wantBody := `method not allowed`
+	if strings.TrimSpace(responseRecorder.Body.String()) != wantBody {
+		t.Errorf("want '%s', got '%s'", wantBody, responseRecorder.Body)
+	}
+}

+ 138 - 0
server/user_manager_mock_test.go

@@ -0,0 +1,138 @@
+// Code generated by mockery v2.38.0. DO NOT EDIT.
+
+package server
+
+import (
+	state "github.com/mkaminski/goaim/state"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// mockUserManager is an autogenerated mock type for the UserManager type
+type mockUserManager struct {
+	mock.Mock
+}
+
+type mockUserManager_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *mockUserManager) EXPECT() *mockUserManager_Expecter {
+	return &mockUserManager_Expecter{mock: &_m.Mock}
+}
+
+// AllUsers provides a mock function with given fields:
+func (_m *mockUserManager) AllUsers() ([]state.User, error) {
+	ret := _m.Called()
+
+	if len(ret) == 0 {
+		panic("no return value specified for AllUsers")
+	}
+
+	var r0 []state.User
+	var r1 error
+	if rf, ok := ret.Get(0).(func() ([]state.User, error)); ok {
+		return rf()
+	}
+	if rf, ok := ret.Get(0).(func() []state.User); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]state.User)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// mockUserManager_AllUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AllUsers'
+type mockUserManager_AllUsers_Call struct {
+	*mock.Call
+}
+
+// AllUsers is a helper method to define mock.On call
+func (_e *mockUserManager_Expecter) AllUsers() *mockUserManager_AllUsers_Call {
+	return &mockUserManager_AllUsers_Call{Call: _e.mock.On("AllUsers")}
+}
+
+func (_c *mockUserManager_AllUsers_Call) Run(run func()) *mockUserManager_AllUsers_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *mockUserManager_AllUsers_Call) Return(_a0 []state.User, _a1 error) *mockUserManager_AllUsers_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *mockUserManager_AllUsers_Call) RunAndReturn(run func() ([]state.User, error)) *mockUserManager_AllUsers_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// InsertUser provides a mock function with given fields: u
+func (_m *mockUserManager) InsertUser(u state.User) error {
+	ret := _m.Called(u)
+
+	if len(ret) == 0 {
+		panic("no return value specified for InsertUser")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(state.User) error); ok {
+		r0 = rf(u)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// mockUserManager_InsertUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InsertUser'
+type mockUserManager_InsertUser_Call struct {
+	*mock.Call
+}
+
+// InsertUser is a helper method to define mock.On call
+//   - u state.User
+func (_e *mockUserManager_Expecter) InsertUser(u interface{}) *mockUserManager_InsertUser_Call {
+	return &mockUserManager_InsertUser_Call{Call: _e.mock.On("InsertUser", u)}
+}
+
+func (_c *mockUserManager_InsertUser_Call) Run(run func(u state.User)) *mockUserManager_InsertUser_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(state.User))
+	})
+	return _c
+}
+
+func (_c *mockUserManager_InsertUser_Call) Return(_a0 error) *mockUserManager_InsertUser_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *mockUserManager_InsertUser_Call) RunAndReturn(run func(state.User) error) *mockUserManager_InsertUser_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// newMockUserManager creates a new instance of mockUserManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func newMockUserManager(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *mockUserManager {
+	mock := &mockUserManager{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}

+ 2 - 2
state/user_store_test.go

@@ -228,7 +228,7 @@ func TestProfileNonExistent(t *testing.T) {
 	assert.Empty(t, prof)
 }
 
-func TestInterestedUsers(t *testing.T) {
+func TestAdjacentUsers(t *testing.T) {
 
 	defer func() {
 		assert.NoError(t, os.Remove(testFile))
@@ -457,7 +457,7 @@ func TestNewStubUser(t *testing.T) {
 	assert.Equal(t, want, have)
 }
 
-func TestSQLiteUserStore_InterestedUsers(t *testing.T) {
+func TestSQLiteUserStore_AdjacentUsers(t *testing.T) {
 	defer func() {
 		assert.NoError(t, os.Remove(testFile))
 	}()