Mike 2 месяцев назад
Родитель
Сommit
2f1deecae4
4 измененных файлов с 158 добавлено и 194 удалено
  1. 1 3
      cmd/server/factory.go
  2. 1 0
      server/icq_legacy/property_test.go
  3. 119 146
      server/icq_legacy/service.go
  4. 37 45
      server/icq_legacy/service_test.go

+ 1 - 3
cmd/server/factory.go

@@ -679,6 +679,7 @@ func ICQLegacy(deps Container) *icq_legacy.LegacyServer {
 		deps.sqLiteUserStore,        // buddyListRegistry
 		deps.sqLiteUserStore,        // buddyListRegistry
 		buddyService,                // buddyService
 		buddyService,                // buddyService
 		deps.icbmSvc,
 		deps.icbmSvc,
+		sessionManager, // legacySessionManager
 		logger,
 		logger,
 	)
 	)
 
 
@@ -726,9 +727,6 @@ func ICQLegacy(deps Container) *icq_legacy.LegacyServer {
 	v4Handler.SetDispatcher(dispatcher)
 	v4Handler.SetDispatcher(dispatcher)
 	v5Handler.SetDispatcher(dispatcher)
 	v5Handler.SetDispatcher(dispatcher)
 
 
-	// Set the legacy session manager on the service (same package, no adapter needed)
-	icqLegacyService.SetLegacySessionManager(sessionManager)
-
 	// Wire up OSCAR->legacy message bridge so OSCAR status notifications
 	// Wire up OSCAR->legacy message bridge so OSCAR status notifications
 	// reach legacy clients via the session message pump
 	// reach legacy clients via the session message pump
 	legacyBridge := icq_legacy.NewLegacyMessageBridge(sessionManager, dispatcher, deps.sqLiteUserStore, logger)
 	legacyBridge := icq_legacy.NewLegacyMessageBridge(sessionManager, dispatcher, deps.sqLiteUserStore, logger)

+ 1 - 0
server/icq_legacy/property_test.go

@@ -77,6 +77,7 @@ func TestProperty_ServiceBehavioralEquivalence(t *testing.T) {
 			newMockBuddyListRegistry(t),
 			newMockBuddyListRegistry(t),
 			newMockBuddyService(t),
 			newMockBuddyService(t),
 			newMockICBMService(t),
 			newMockICBMService(t),
+			&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 			slog.Default(),
 			slog.Default(),
 		)
 		)
 
 

+ 119 - 146
server/icq_legacy/service.go

@@ -41,16 +41,14 @@ type ICQLegacyService struct {
 	relationshipFetcher   RelationshipFetcher
 	relationshipFetcher   RelationshipFetcher
 	buddyListRegistry     BuddyListRegistry
 	buddyListRegistry     BuddyListRegistry
 	buddyService          BuddyService
 	buddyService          BuddyService
-	logger                *slog.Logger
-	timeNow               func() time.Time
-
-	// legacySessionManager is set by the server package
+	// legacySessionManager tracks legacy ICQ UDP sessions for presence, messaging,
+	// and contact notifications (shared with protocol handlers).
 	legacySessionManager *LegacySessionManager
 	legacySessionManager *LegacySessionManager
+	logger               *slog.Logger
+	timeNow              func() time.Time
 }
 }
 
 
 // NewICQLegacyService creates a new ICQLegacyService with the given dependencies.
 // NewICQLegacyService creates a new ICQLegacyService with the given dependencies.
-// The legacy session manager must be set separately via SetLegacySessionManager
-// after the server package initializes it, to avoid circular dependencies.
 func NewICQLegacyService(
 func NewICQLegacyService(
 	authService AuthService,
 	authService AuthService,
 	userManager UserManager,
 	userManager UserManager,
@@ -66,6 +64,7 @@ func NewICQLegacyService(
 	buddyListRegistry BuddyListRegistry,
 	buddyListRegistry BuddyListRegistry,
 	buddyService BuddyService,
 	buddyService BuddyService,
 	icbmSvc ICBMService,
 	icbmSvc ICBMService,
+	legacySessionManager *LegacySessionManager,
 	logger *slog.Logger,
 	logger *slog.Logger,
 ) *ICQLegacyService {
 ) *ICQLegacyService {
 	return &ICQLegacyService{
 	return &ICQLegacyService{
@@ -83,19 +82,12 @@ func NewICQLegacyService(
 		buddyListRegistry:     buddyListRegistry,
 		buddyListRegistry:     buddyListRegistry,
 		buddyService:          buddyService,
 		buddyService:          buddyService,
 		icbmService:           icbmSvc,
 		icbmService:           icbmSvc,
+		legacySessionManager:  legacySessionManager,
 		logger:                logger,
 		logger:                logger,
 		timeNow:               time.Now,
 		timeNow:               time.Now,
 	}
 	}
 }
 }
 
 
-// SetLegacySessionManager sets the legacy session manager used for checking
-// online status and determining notification targets. This is called by the
-// server package after initialization to break the circular dependency between
-// the foodgroup and server/icq_legacy packages.
-func (s *ICQLegacyService) SetLegacySessionManager(mgr *LegacySessionManager) {
-	s.legacySessionManager = mgr
-}
-
 // ValidateCredentials checks if the given UIN and password are valid.
 // ValidateCredentials checks if the given UIN and password are valid.
 // Returns true if credentials are valid, false otherwise.
 // Returns true if credentials are valid, false otherwise.
 // The password is validated by AuthService without registering a BOS session.
 // The password is validated by AuthService without registering a BOS session.
@@ -174,9 +166,7 @@ func (s *ICQLegacyService) AuthenticateUser(ctx context.Context, req AuthRequest
 
 
 	instance, err := s.authService.RegisterBOSSession(ctx, serverCookie, func(sess *state.Session) {
 	instance, err := s.authService.RegisterBOSSession(ctx, serverCookie, func(sess *state.Session) {
 		sess.OnSessionClose(func() {
 		sess.OnSessionClose(func() {
-			if s.legacySessionManager != nil {
-				s.legacySessionManager.RemoveSession(req.UIN)
-			}
+			s.legacySessionManager.RemoveSession(req.UIN)
 		})
 		})
 	})
 	})
 	if err != nil {
 	if err != nil {
@@ -294,24 +284,22 @@ func (s *ICQLegacyService) ProcessContactList(ctx context.Context, instance *sta
 		}
 		}
 
 
 		// Check if contact is online via legacy session manager
 		// Check if contact is online via legacy session manager
-		if s.legacySessionManager != nil {
-			legacySession := s.legacySessionManager.GetSession(contactUIN)
-			if legacySession != nil {
-				// Check visibility — invisible users should not appear online
-				// unless the viewer is on their visible list
-				contactStatus := legacySession.GetStatus()
-				visible := true
-				if contactStatus&ICQLegacyStatusInvisible != 0 {
-					visible = legacySession.IsOnVisibleList(req.UIN)
-				}
-				if visible {
-					status.Online = true
-					status.Status = contactStatus
-					s.logger.Debug("ProcessContactList: contact online (legacy)",
-						"contact_uin", contactUIN,
-						"status", fmt.Sprintf("0x%08X", status.Status),
-					)
-				}
+		legacySession := s.legacySessionManager.GetSession(contactUIN)
+		if legacySession != nil {
+			// Check visibility — invisible users should not appear online
+			// unless the viewer is on their visible list
+			contactStatus := legacySession.GetStatus()
+			visible := true
+			if contactStatus&ICQLegacyStatusInvisible != 0 {
+				visible = legacySession.IsOnVisibleList(req.UIN)
+			}
+			if visible {
+				status.Online = true
+				status.Status = contactStatus
+				s.logger.Debug("ProcessContactList: contact online (legacy)",
+					"contact_uin", contactUIN,
+					"status", fmt.Sprintf("0x%08X", status.Status),
+				)
 			}
 			}
 		}
 		}
 
 
@@ -409,20 +397,18 @@ func (s *ICQLegacyService) ProcessUserAdd(ctx context.Context, instance *state.S
 	}
 	}
 
 
 	// Check if target user is online via legacy session manager
 	// Check if target user is online via legacy session manager
-	if s.legacySessionManager != nil {
-		legacySession := s.legacySessionManager.GetSession(req.TargetUIN)
-		if legacySession != nil {
-			result.TargetOnline = true
-			result.TargetStatus = legacySession.GetStatus()
-			// SendYouWereAdded is true for legacy clients so they know someone added them
-			result.SendYouWereAdded = true
-
-			s.logger.Debug("ProcessUserAdd: target online (legacy)",
-				"target_uin", req.TargetUIN,
-				"status", fmt.Sprintf("0x%08X", result.TargetStatus),
-			)
-			return result, nil
-		}
+	legacySession := s.legacySessionManager.GetSession(req.TargetUIN)
+	if legacySession != nil {
+		result.TargetOnline = true
+		result.TargetStatus = legacySession.GetStatus()
+		// SendYouWereAdded is true for legacy clients so they know someone added them
+		result.SendYouWereAdded = true
+
+		s.logger.Debug("ProcessUserAdd: target online (legacy)",
+			"target_uin", req.TargetUIN,
+			"status", fmt.Sprintf("0x%08X", result.TargetStatus),
+		)
+		return result, nil
 	}
 	}
 
 
 	// Check if target user is online via OSCAR session
 	// Check if target user is online via OSCAR session
@@ -486,22 +472,20 @@ func (s *ICQLegacyService) ProcessMessage(ctx context.Context, session *LegacySe
 	)
 	)
 
 
 	// Check if target user is online via legacy session manager
 	// Check if target user is online via legacy session manager
-	if s.legacySessionManager != nil {
-		legacySession := s.legacySessionManager.GetSession(req.ToUIN)
-		if legacySession != nil {
-			// Target is online via legacy protocol
-			result.TargetOnline = true
-			result.Delivered = true
-			// Get the protocol version from the session
-			// The handler will use this to route to the correct protocol handler
-			result.TargetVersion = ICQLegacyVersionV5 // Default, actual version determined by session
-
-			s.logger.Debug("ProcessMessage: target online (legacy)",
-				"to", req.ToUIN,
-				"status", fmt.Sprintf("0x%08X", legacySession.GetStatus()),
-			)
-			return result, nil
-		}
+	legacySession := s.legacySessionManager.GetSession(req.ToUIN)
+	if legacySession != nil {
+		// Target is online via legacy protocol
+		result.TargetOnline = true
+		result.Delivered = true
+		// Get the protocol version from the session
+		// The handler will use this to route to the correct protocol handler
+		result.TargetVersion = ICQLegacyVersionV5 // Default, actual version determined by session
+
+		s.logger.Debug("ProcessMessage: target online (legacy)",
+			"to", req.ToUIN,
+			"status", fmt.Sprintf("0x%08X", legacySession.GetStatus()),
+		)
+		return result, nil
 	}
 	}
 
 
 	// Check if target user is online via OSCAR session
 	// Check if target user is online via OSCAR session
@@ -814,17 +798,15 @@ func (s *ICQLegacyService) GetUserInfoForProtocol(ctx context.Context, targetUIN
 
 
 	// Check if user is online
 	// Check if user is online
 	// First check legacy sessions
 	// First check legacy sessions
-	if s.legacySessionManager != nil {
-		legacySession := s.legacySessionManager.GetSession(targetUIN)
-		if legacySession != nil {
-			result.Online = true
-			result.Status = legacySession.GetStatus()
-			s.logger.Debug("GetUserInfoForProtocol: user online (legacy)",
-				"uin", targetUIN,
-				"status", fmt.Sprintf("0x%08X", result.Status),
-			)
-			return result, nil
-		}
+	legacySession := s.legacySessionManager.GetSession(targetUIN)
+	if legacySession != nil {
+		result.Online = true
+		result.Status = legacySession.GetStatus()
+		s.logger.Debug("GetUserInfoForProtocol: user online (legacy)",
+			"uin", targetUIN,
+			"status", fmt.Sprintf("0x%08X", result.Status),
+		)
+		return result, nil
 	}
 	}
 
 
 	// Check OSCAR sessions
 	// Check OSCAR sessions
@@ -1139,18 +1121,15 @@ func (s *ICQLegacyService) ProcessStatusChange(ctx context.Context, req StatusCh
 	// Get all users who have this user in their contact list
 	// Get all users who have this user in their contact list
 	// These are the users who should be notified of the status change
 	// These are the users who should be notified of the status change
 	// Use the legacy session manager's NotifyContactsOfStatus method
 	// Use the legacy session manager's NotifyContactsOfStatus method
-	if s.legacySessionManager != nil {
-		// Get the session for the user whose status is changing
-		session := s.legacySessionManager.GetSession(req.UIN)
-		if session != nil {
-			// Get all UINs that should be notified (users who have this user in their contact list)
-			contactsToNotify := s.legacySessionManager.NotifyContactsOfStatus(session)
-			for _, contactUIN := range contactsToNotify {
-				result.NotifyTargets = append(result.NotifyTargets, NotifyTarget{
-					UIN:     contactUIN,
-					Version: 0, // Version will be determined by handler from session
-				})
-			}
+	session := s.legacySessionManager.GetSession(req.UIN)
+	if session != nil {
+		// Get all UINs that should be notified (users who have this user in their contact list)
+		contactsToNotify := s.legacySessionManager.NotifyContactsOfStatus(session)
+		for _, contactUIN := range contactsToNotify {
+			result.NotifyTargets = append(result.NotifyTargets, NotifyTarget{
+				UIN:     contactUIN,
+				Version: 0, // Version will be determined by handler from session
+			})
 		}
 		}
 	}
 	}
 
 
@@ -1160,27 +1139,25 @@ func (s *ICQLegacyService) ProcessStatusChange(ctx context.Context, req StatusCh
 
 
 	// Update the legacy session's OSCAR instance status and broadcast
 	// Update the legacy session's OSCAR instance status and broadcast
 	// using session.TLVUserInfo() — exactly like OSCAR's SetUserInfoFields does.
 	// using session.TLVUserInfo() — exactly like OSCAR's SetUserInfoFields does.
-	if s.legacySessionManager != nil {
-		session := s.legacySessionManager.GetSession(req.UIN)
-		if session != nil && session.Instance != nil {
-			oscarStatus := mapLegacyStatusToOSCAR(req.NewStatus)
-			session.Instance.SetUserStatusBitmask(oscarStatus)
-			if oscarStatus != wire.OServiceUserStatusAvailable {
-				session.Instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
-			} else {
-				session.Instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
-			}
+	statusSession := s.legacySessionManager.GetSession(req.UIN)
+	if statusSession != nil && statusSession.Instance != nil {
+		oscarStatus := mapLegacyStatusToOSCAR(req.NewStatus)
+		statusSession.Instance.SetUserStatusBitmask(oscarStatus)
+		if oscarStatus != wire.OServiceUserStatusAvailable {
+			statusSession.Instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
+		} else {
+			statusSession.Instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
+		}
 
 
-			// Mirror OSCAR's SetUserInfoFields: if invisible, send departure;
-			// otherwise send arrival with updated TLVUserInfo.
-			if session.Instance.Session().Invisible() {
-				if err := s.buddyBroadcaster.BroadcastBuddyDeparted(ctx, screenName); err != nil {
-					s.logger.Debug("ProcessStatusChange: failed to broadcast departure", "err", err)
-				}
-			} else {
-				if err := s.buddyBroadcaster.BroadcastBuddyArrived(ctx, screenName, session.Instance.Session().TLVUserInfo()); err != nil {
-					s.logger.Debug("ProcessStatusChange: failed to broadcast to OSCAR clients", "err", err)
-				}
+		// Mirror OSCAR's SetUserInfoFields: if invisible, send departure;
+		// otherwise send arrival with updated TLVUserInfo.
+		if statusSession.Instance.Session().Invisible() {
+			if err := s.buddyBroadcaster.BroadcastBuddyDeparted(ctx, screenName); err != nil {
+				s.logger.Debug("ProcessStatusChange: failed to broadcast departure", "err", err)
+			}
+		} else {
+			if err := s.buddyBroadcaster.BroadcastBuddyArrived(ctx, screenName, statusSession.Instance.Session().TLVUserInfo()); err != nil {
+				s.logger.Debug("ProcessStatusChange: failed to broadcast to OSCAR clients", "err", err)
 			}
 			}
 		}
 		}
 	}
 	}
@@ -1202,30 +1179,28 @@ func (s *ICQLegacyService) NotifyStatusChange(ctx context.Context, uin uint32, s
 	oscarStatus := mapLegacyStatusToOSCAR(status)
 	oscarStatus := mapLegacyStatusToOSCAR(status)
 
 
 	// Update the OSCAR instance so session.TLVUserInfo() is correct
 	// Update the OSCAR instance so session.TLVUserInfo() is correct
-	if s.legacySessionManager != nil {
-		session := s.legacySessionManager.GetSession(uin)
-		if session != nil && session.Instance != nil {
-			session.Instance.SetUserStatusBitmask(oscarStatus)
-			if oscarStatus != wire.OServiceUserStatusAvailable {
-				session.Instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
-			} else {
-				session.Instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
-			}
-
-			// Invisible: send departure so OSCAR clients see user as offline
-			if status&ICQLegacyStatusInvisible != 0 {
-				if err := s.buddyBroadcaster.BroadcastBuddyDeparted(ctx, screenName); err != nil {
-					s.logger.Debug("NotifyStatusChange: failed to broadcast departure", "err", err)
-				}
-				return nil
-			}
+	notifySession := s.legacySessionManager.GetSession(uin)
+	if notifySession != nil && notifySession.Instance != nil {
+		notifySession.Instance.SetUserStatusBitmask(oscarStatus)
+		if oscarStatus != wire.OServiceUserStatusAvailable {
+			notifySession.Instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
+		} else {
+			notifySession.Instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
+		}
 
 
-			userInfo := session.Instance.Session().TLVUserInfo()
-			if err := s.buddyBroadcaster.BroadcastBuddyArrived(ctx, screenName, userInfo); err != nil {
-				s.logger.Debug("NotifyStatusChange: failed to broadcast arrival", "err", err)
+		// Invisible: send departure so OSCAR clients see user as offline
+		if status&ICQLegacyStatusInvisible != 0 {
+			if err := s.buddyBroadcaster.BroadcastBuddyDeparted(ctx, screenName); err != nil {
+				s.logger.Debug("NotifyStatusChange: failed to broadcast departure", "err", err)
 			}
 			}
 			return nil
 			return nil
 		}
 		}
+
+		userInfo := notifySession.Instance.Session().TLVUserInfo()
+		if err := s.buddyBroadcaster.BroadcastBuddyArrived(ctx, screenName, userInfo); err != nil {
+			s.logger.Debug("NotifyStatusChange: failed to broadcast arrival", "err", err)
+		}
+		return nil
 	}
 	}
 
 
 	// Fallback if no session/instance — invisible should not broadcast arrival
 	// Fallback if no session/instance — invisible should not broadcast arrival
@@ -1274,27 +1249,25 @@ func (s *ICQLegacyService) NotifyUserOnline(ctx context.Context, uin uint32, sta
 	// Update the legacy session's OSCAR instance status bitmask so that
 	// Update the legacy session's OSCAR instance status bitmask so that
 	// session.TLVUserInfo() reflects the correct status.
 	// session.TLVUserInfo() reflects the correct status.
 	oscarStatus := mapLegacyStatusToOSCAR(status)
 	oscarStatus := mapLegacyStatusToOSCAR(status)
-	if s.legacySessionManager != nil {
-		session := s.legacySessionManager.GetSession(uin)
-		if session != nil && session.Instance != nil {
-			session.Instance.SetUserStatusBitmask(oscarStatus)
-			if oscarStatus != wire.OServiceUserStatusAvailable {
-				session.Instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
-			} else {
-				session.Instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
-			}
+	onlineSession := s.legacySessionManager.GetSession(uin)
+	if onlineSession != nil && onlineSession.Instance != nil {
+		onlineSession.Instance.SetUserStatusBitmask(oscarStatus)
+		if oscarStatus != wire.OServiceUserStatusAvailable {
+			onlineSession.Instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
+		} else {
+			onlineSession.Instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
+		}
 
 
-			// Invisible: don't broadcast arrival — user should appear offline
-			if status&ICQLegacyStatusInvisible != 0 {
-				return nil
-			}
+		// Invisible: don't broadcast arrival — user should appear offline
+		if status&ICQLegacyStatusInvisible != 0 {
+			return nil
+		}
 
 
-			// Use the session's TLVUserInfo — same as OSCAR's SetUserInfoFields
-			userInfo := session.Instance.Session().TLVUserInfo()
+		// Use the session's TLVUserInfo — same as OSCAR's SetUserInfoFields
+		userInfo := onlineSession.Instance.Session().TLVUserInfo()
 
 
-			if err := s.buddyBroadcaster.BroadcastBuddyArrived(ctx, screenName, userInfo); err != nil {
-				s.logger.Debug("NotifyUserOnline: failed to broadcast arrival", "uin", uin, "err", err)
-			}
+		if err := s.buddyBroadcaster.BroadcastBuddyArrived(ctx, screenName, userInfo); err != nil {
+			s.logger.Debug("NotifyUserOnline: failed to broadcast arrival", "uin", uin, "err", err)
 		}
 		}
 	}
 	}
 
 

+ 37 - 45
server/icq_legacy/service_test.go

@@ -147,6 +147,7 @@ func TestICQLegacyService_AuthenticateUser(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
@@ -175,8 +176,7 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 		req        MessageRequest
 		req        MessageRequest
 		wantResult *MessageResult
 		wantResult *MessageResult
 		wantErr    error
 		wantErr    error
-		// legacySessionManager is set to non-nil when we need legacy session lookup
-		setupLegacyMgr func(t *testing.T, svc *ICQLegacyService)
+		legacyMgr  *LegacySessionManager
 	}{
 	}{
 		{
 		{
 			name: "target online - legacy session exists",
 			name: "target online - legacy session exists",
@@ -186,13 +186,10 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 				MsgType: ICQLegacyMsgText,
 				MsgType: ICQLegacyMsgText,
 				Message: "hello",
 				Message: "hello",
 			},
 			},
-			setupLegacyMgr: func(t *testing.T, svc *ICQLegacyService) {
-				mgr := &LegacySessionManager{
-					sessions: map[uint32]*LegacySession{
-						22222: newTestLegacySession(22222),
-					},
-				}
-				svc.SetLegacySessionManager(mgr)
+			legacyMgr: &LegacySessionManager{
+				sessions: map[uint32]*LegacySession{
+					22222: newTestLegacySession(22222),
+				},
 			},
 			},
 			wantResult: &MessageResult{
 			wantResult: &MessageResult{
 				Delivered:     true,
 				Delivered:     true,
@@ -243,6 +240,7 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 				TargetOnline:  true,
 				TargetOnline:  true,
 				TargetVersion: 0,
 				TargetVersion: 0,
 			},
 			},
+			legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 		},
 		},
 		{
 		{
 			name: "target offline - stored",
 			name: "target offline - stored",
@@ -295,6 +293,7 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 				TargetOnline:  false,
 				TargetOnline:  false,
 				TargetVersion: 0,
 				TargetVersion: 0,
 			},
 			},
+			legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 		},
 		},
 		{
 		{
 			name: "invalid sender UIN (0)",
 			name: "invalid sender UIN (0)",
@@ -309,6 +308,7 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 				StoredOffline: false,
 				StoredOffline: false,
 				TargetOnline:  false,
 				TargetOnline:  false,
 			},
 			},
+			legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 		},
 		},
 		{
 		{
 			name: "invalid target UIN (0)",
 			name: "invalid target UIN (0)",
@@ -323,6 +323,7 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 				StoredOffline: false,
 				StoredOffline: false,
 				TargetOnline:  false,
 				TargetOnline:  false,
 			},
 			},
+			legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 		},
 		},
 	}
 	}
 
 
@@ -351,13 +352,10 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				icbmSvc,
 				icbmSvc,
+				tc.legacyMgr,
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
-			if tc.setupLegacyMgr != nil {
-				tc.setupLegacyMgr(t, svc)
-			}
-
 			got, err := svc.ProcessMessage(context.Background(), tc.sess, tc.req)
 			got, err := svc.ProcessMessage(context.Background(), tc.sess, tc.req)
 
 
 			if tc.wantErr != nil {
 			if tc.wantErr != nil {
@@ -375,11 +373,11 @@ func TestICQLegacyService_ProcessMessage(t *testing.T) {
 
 
 func TestICQLegacyService_ProcessContactList(t *testing.T) {
 func TestICQLegacyService_ProcessContactList(t *testing.T) {
 	tests := []struct {
 	tests := []struct {
-		name           string
-		mockParams     mockParams
-		req            ContactListRequest
-		setupLegacyMgr func(t *testing.T, svc *ICQLegacyService)
-		wantResult     *ContactListResult
+		name       string
+		mockParams mockParams
+		req        ContactListRequest
+		legacyMgr  *LegacySessionManager
+		wantResult *ContactListResult
 	}{
 	}{
 		{
 		{
 			name: "mixed online/offline contacts",
 			name: "mixed online/offline contacts",
@@ -401,11 +399,8 @@ func TestICQLegacyService_ProcessContactList(t *testing.T) {
 					},
 					},
 				},
 				},
 			},
 			},
-			setupLegacyMgr: func(t *testing.T, svc *ICQLegacyService) {
-				mgr := &LegacySessionManager{
-					sessions: make(map[uint32]*LegacySession),
-				}
-				svc.SetLegacySessionManager(mgr)
+			legacyMgr: &LegacySessionManager{
+				sessions: make(map[uint32]*LegacySession),
 			},
 			},
 			wantResult: &ContactListResult{
 			wantResult: &ContactListResult{
 				OnlineContacts: []ContactStatus{
 				OnlineContacts: []ContactStatus{
@@ -420,6 +415,7 @@ func TestICQLegacyService_ProcessContactList(t *testing.T) {
 				UIN:      11111,
 				UIN:      11111,
 				Contacts: []uint32{},
 				Contacts: []uint32{},
 			},
 			},
+			legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 			wantResult: &ContactListResult{
 			wantResult: &ContactListResult{
 				OnlineContacts: []ContactStatus{},
 				OnlineContacts: []ContactStatus{},
 			},
 			},
@@ -462,12 +458,10 @@ func TestICQLegacyService_ProcessContactList(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				buddySvc,
 				buddySvc,
 				newMockICBMService(t),
 				newMockICBMService(t),
+				tc.legacyMgr,
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
-			if tc.setupLegacyMgr != nil {
-				tc.setupLegacyMgr(t, svc)
-			}
 			var instance *state.SessionInstance
 			var instance *state.SessionInstance
 			if tc.req.UIN != 0 {
 			if tc.req.UIN != 0 {
 				instance = newTestOSCARInstance(state.DisplayScreenName(strconv.FormatUint(uint64(tc.req.UIN), 10)))
 				instance = newTestOSCARInstance(state.DisplayScreenName(strconv.FormatUint(uint64(tc.req.UIN), 10)))
@@ -490,11 +484,11 @@ func TestICQLegacyService_ProcessContactList(t *testing.T) {
 
 
 func TestICQLegacyService_ProcessStatusChange(t *testing.T) {
 func TestICQLegacyService_ProcessStatusChange(t *testing.T) {
 	tests := []struct {
 	tests := []struct {
-		name           string
-		mockParams     mockParams
-		req            StatusChangeRequest
-		setupLegacyMgr func(t *testing.T, svc *ICQLegacyService)
-		wantTargets    int
+		name        string
+		mockParams  mockParams
+		req         StatusChangeRequest
+		legacyMgr   *LegacySessionManager
+		wantTargets int
 	}{
 	}{
 		{
 		{
 			name: "status change with notification targets",
 			name: "status change with notification targets",
@@ -503,16 +497,11 @@ func TestICQLegacyService_ProcessStatusChange(t *testing.T) {
 				OldStatus: ICQLegacyStatusOnline,
 				OldStatus: ICQLegacyStatusOnline,
 				NewStatus: ICQLegacyStatusAway,
 				NewStatus: ICQLegacyStatusAway,
 			},
 			},
-			setupLegacyMgr: func(t *testing.T, svc *ICQLegacyService) {
-				sess := newTestLegacySession(11111, legacySessionOptContactList([]uint32{22222}))
-				contactSess := newTestLegacySession(22222, legacySessionOptContactList([]uint32{11111}))
-				mgr := &LegacySessionManager{
-					sessions: map[uint32]*LegacySession{
-						11111: sess,
-						22222: contactSess,
-					},
-				}
-				svc.SetLegacySessionManager(mgr)
+			legacyMgr: &LegacySessionManager{
+				sessions: map[uint32]*LegacySession{
+					11111: newTestLegacySession(11111, legacySessionOptContactList([]uint32{22222})),
+					22222: newTestLegacySession(22222, legacySessionOptContactList([]uint32{11111})),
+				},
 			},
 			},
 			wantTargets: 1,
 			wantTargets: 1,
 		},
 		},
@@ -523,6 +512,7 @@ func TestICQLegacyService_ProcessStatusChange(t *testing.T) {
 				OldStatus: ICQLegacyStatusOnline,
 				OldStatus: ICQLegacyStatusOnline,
 				NewStatus: ICQLegacyStatusAway,
 				NewStatus: ICQLegacyStatusAway,
 			},
 			},
+			legacyMgr:   &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 			wantTargets: 0,
 			wantTargets: 0,
 		},
 		},
 	}
 	}
@@ -544,13 +534,10 @@ func TestICQLegacyService_ProcessStatusChange(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				tc.legacyMgr,
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
-			if tc.setupLegacyMgr != nil {
-				tc.setupLegacyMgr(t, svc)
-			}
-
 			got, err := svc.ProcessStatusChange(context.Background(), tc.req)
 			got, err := svc.ProcessStatusChange(context.Background(), tc.req)
 
 
 			assert.NoError(t, err)
 			assert.NoError(t, err)
@@ -652,6 +639,7 @@ func TestICQLegacyService_SearchByUIN(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
@@ -783,6 +771,7 @@ func TestICQLegacyService_SearchByName(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
@@ -876,6 +865,7 @@ func TestICQLegacyService_GetOfflineMessages(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
@@ -929,6 +919,7 @@ func TestICQLegacyService_RegisterNewUser(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 				slog.Default(),
 				slog.Default(),
 			)
 			)
 
 
@@ -1037,6 +1028,7 @@ func TestICQLegacyService_DeleteUser(t *testing.T) {
 				newMockBuddyListRegistry(t),
 				newMockBuddyListRegistry(t),
 				newMockBuddyService(t),
 				newMockBuddyService(t),
 				newMockICBMService(t),
 				newMockICBMService(t),
+				&LegacySessionManager{sessions: map[uint32]*LegacySession{}},
 				slog.Default(),
 				slog.Default(),
 			)
 			)