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

increase test coverage on session message handling

Mike 11 месяцев назад
Родитель
Сommit
846c425bae
1 измененных файлов с 275 добавлено и 0 удалено
  1. 275 0
      server/oscar/server_test.go

+ 275 - 0
server/oscar/server_test.go

@@ -671,3 +671,278 @@ func Test_oscarServer_dispatchIncomingMessages_disconnect(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
 }
+
+func Test_oscarServer_receiveSessMessages_BOS_integration(t *testing.T) {
+	serverConn, clientConn := net.Pipe()
+	defer serverConn.Close()
+	defer clientConn.Close()
+
+	// Prepare session and mocks so we can exercise through routeConnection
+	sess := state.NewSession()
+
+	authService := newMockAuthService(t)
+	authService.EXPECT().
+		CrackCookie(mock.Anything).
+		Return(state.ServerCookie{Service: wire.BOS}, nil)
+	authService.EXPECT().
+		RegisterBOSSession(mock.Anything, state.ServerCookie{Service: wire.BOS}).
+		Return(sess, nil)
+
+	var signoutWG sync.WaitGroup
+	signoutWG.Add(1)
+	authService.EXPECT().
+		Signout(mock.Anything, sess).
+		Run(func(ctx context.Context, s *state.Session) { signoutWG.Done() })
+
+	onlineNotifier := newMockOnlineNotifier(t)
+	onlineNotifier.EXPECT().
+		HostOnline(mock.Anything).
+		Return(wire.SNACMessage{
+			Frame: wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
+			Body:  wire.SNAC_0x01_0x03_OServiceHostOnline{},
+		})
+
+	buddyListRegistry := newMockBuddyListRegistry(t)
+	buddyListRegistry.EXPECT().RegisterBuddyList(mock.Anything, mock.Anything).Return(nil)
+	buddyListRegistry.EXPECT().UnregisterBuddyList(mock.Anything, mock.Anything).Return(nil)
+
+	departureNotifier := newMockDepartureNotifier(t)
+	departureNotifier.EXPECT().BroadcastBuddyDeparted(mock.Anything, mock.Anything).Return(nil)
+
+	chatSessionManager := newMockChatSessionManager(t)
+	chatSessionManager.EXPECT().RemoveUserFromAllChats(mock.Anything)
+
+	server := oscarServer{
+		AuthService:        authService,
+		BuddyListRegistry:  buddyListRegistry,
+		ChatSessionManager: chatSessionManager,
+		DepartureNotifier:  departureNotifier,
+		OnlineNotifier:     onlineNotifier,
+		Logger:             slog.New(slog.NewTextHandler(io.Discard, nil)),
+	}
+
+	// Fake client connection with address
+	addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
+	assert.NoError(t, err)
+	clientFake := fakeConn{Conn: serverConn, local: addr, remote: addr}
+
+	// Coordinate when the server has finished login and sent HostOnline
+	ready := make(chan struct{})
+
+	// Client goroutine: perform handshake and then read forwarded messages
+	go func() {
+		// < receive FLAPSignonFrame
+		flap := wire.FLAPFrame{}
+		_ = wire.UnmarshalBE(&flap, clientConn)
+		flapSignon := wire.FLAPSignonFrame{}
+		_ = wire.UnmarshalBE(&flapSignon, bytes.NewBuffer(flap.Payload))
+
+		// > send FLAPSignonFrame with login cookie
+		flapSignon = wire.FLAPSignonFrame{FLAPVersion: 1}
+		flapSignon.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
+		buf := &bytes.Buffer{}
+		_ = wire.MarshalBE(flapSignon, buf)
+		_ = wire.MarshalBE(wire.FLAPFrame{StartMarker: 42, FrameType: wire.FLAPFrameSignon, Payload: buf.Bytes()}, clientConn)
+
+		// Expect HostOnline
+		flapcClient := wire.NewFlapClient(0, clientConn, clientConn)
+		fr := wire.SNACFrame{}
+		body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
+		_ = flapcClient.ReceiveSNAC(&fr, &body)
+		close(ready)
+	}()
+
+	// Run the server handler in background so we can drive the session
+	doneServer := make(chan error, 1)
+	go func() { doneServer <- server.routeConnection(context.Background(), clientFake, "") }()
+
+	// Wait for HostOnline to be received so session is ready
+	select {
+	case <-ready:
+	case <-time.After(5 * time.Second):
+		t.Fatal("server did not complete login in time")
+	}
+
+	// Now send messages via the session and verify client receives them
+	messages := []wire.SNACMessage{
+		{
+			Frame: wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived},
+			Body:  wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "user1"}},
+		},
+		{
+			Frame: wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
+			Body:  wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "user2"}},
+		},
+		{
+			Frame: wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: 0x07},
+			Body:  wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{Cookie: 12345, ChannelID: 1, TLVUserInfo: wire.TLVUserInfo{ScreenName: "sender"}},
+		},
+	}
+
+	for i, msg := range messages {
+		status := sess.RelayMessage(msg)
+		assert.Equal(t, state.SessSendOK, status, "Message %d should be sent successfully", i)
+	}
+
+	// Read and verify all messages from client side
+	for i, expected := range messages {
+		flapFrame := wire.FLAPFrame{}
+		err := wire.UnmarshalBE(&flapFrame, clientConn)
+		assert.NoError(t, err, "read FLAP frame %d", i)
+		assert.Equal(t, uint8(42), flapFrame.StartMarker)
+		assert.Equal(t, wire.FLAPFrameData, flapFrame.FrameType)
+
+		snac := wire.SNACFrame{}
+		buf := bytes.NewBuffer(flapFrame.Payload)
+		err = wire.UnmarshalBE(&snac, buf)
+		assert.NoError(t, err, "unmarshal SNAC %d", i)
+		assert.Equal(t, expected.Frame.FoodGroup, snac.FoodGroup)
+		assert.Equal(t, expected.Frame.SubGroup, snac.SubGroup)
+	}
+
+	// Close client to let server exit cleanly
+	_ = clientConn.Close()
+
+	// Wait for server handler to return
+	select {
+	case err := <-doneServer:
+		assert.NoError(t, err)
+	case <-time.After(5 * time.Second):
+		t.Fatal("routeConnection did not exit in time")
+	}
+
+	// Ensure signout ran
+	signoutWG.Wait()
+}
+
+func Test_oscarServer_receiveSessMessages_Chat_integration(t *testing.T) {
+	serverConn, clientConn := net.Pipe()
+	defer serverConn.Close()
+	defer clientConn.Close()
+
+	// Prepare session and mocks so we can exercise through routeConnection
+	sess := state.NewSession()
+
+	authService := newMockAuthService(t)
+	authService.EXPECT().
+		CrackCookie(mock.Anything).
+		Return(state.ServerCookie{Service: wire.Chat}, nil)
+	authService.EXPECT().
+		RegisterChatSession(mock.Anything, state.ServerCookie{Service: wire.Chat}).
+		Return(sess, nil)
+
+	var signoutWG sync.WaitGroup
+	signoutWG.Add(1)
+	authService.EXPECT().
+		SignoutChat(mock.Anything, sess).
+		Run(func(ctx context.Context, s *state.Session) { signoutWG.Done() })
+
+	onlineNotifier := newMockOnlineNotifier(t)
+	onlineNotifier.EXPECT().
+		HostOnline(mock.Anything).
+		Return(wire.SNACMessage{
+			Frame: wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
+			Body:  wire.SNAC_0x01_0x03_OServiceHostOnline{},
+		})
+
+	server := oscarServer{
+		AuthService:    authService,
+		OnlineNotifier: onlineNotifier,
+		Logger:         slog.New(slog.NewTextHandler(io.Discard, nil)),
+	}
+
+	// Fake client connection with address
+	addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
+	assert.NoError(t, err)
+	clientFake := fakeConn{Conn: serverConn, local: addr, remote: addr}
+
+	ready := make(chan struct{})
+
+	// Client goroutine: perform handshake and then read forwarded messages
+	go func() {
+		// < receive FLAPSignonFrame
+		flap := wire.FLAPFrame{}
+		_ = wire.UnmarshalBE(&flap, clientConn)
+		flapSignon := wire.FLAPSignonFrame{}
+		_ = wire.UnmarshalBE(&flapSignon, bytes.NewBuffer(flap.Payload))
+
+		// > send FLAPSignonFrame with login cookie
+		flapSignon = wire.FLAPSignonFrame{FLAPVersion: 1}
+		flapSignon.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
+		buf := &bytes.Buffer{}
+		_ = wire.MarshalBE(flapSignon, buf)
+		_ = wire.MarshalBE(wire.FLAPFrame{StartMarker: 42, FrameType: wire.FLAPFrameSignon, Payload: buf.Bytes()}, clientConn)
+
+		// Expect HostOnline
+		flapcClient := wire.NewFlapClient(0, clientConn, clientConn)
+		fr := wire.SNACFrame{}
+		body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
+		_ = flapcClient.ReceiveSNAC(&fr, &body)
+		close(ready)
+	}()
+
+	// Run the server handler in background so we can drive the session
+	doneServer := make(chan error, 1)
+	go func() { doneServer <- server.routeConnection(context.Background(), clientFake, "") }()
+
+	// Wait for HostOnline to be received so session is ready
+	select {
+	case <-ready:
+	case <-time.After(5 * time.Second):
+		t.Fatal("server did not complete login in time")
+	}
+
+	messages := []wire.SNACMessage{
+		{
+			Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatUsersJoined},
+			Body:  wire.SNAC_0x0E_0x03_ChatUsersJoined{Users: []wire.TLVUserInfo{{ScreenName: "user1"}}},
+		},
+		{
+			Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatUsersLeft},
+			Body:  wire.SNAC_0x0E_0x04_ChatUsersLeft{Users: []wire.TLVUserInfo{{ScreenName: "user2"}}},
+		},
+		{
+			Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToClient},
+			Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
+				Cookie: 12345, Channel: 1,
+				TLVRestBlock: wire.TLVRestBlock{TLVList: wire.TLVList{
+					wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{ScreenName: "sender"}),
+					wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{TLVList: wire.TLVList{
+						wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello chat!"),
+					}}),
+				}},
+			},
+		},
+	}
+
+	for i, msg := range messages {
+		status := sess.RelayMessage(msg)
+		assert.Equal(t, state.SessSendOK, status, "Message %d should be sent successfully", i)
+	}
+
+	for i, expected := range messages {
+		flapFrame := wire.FLAPFrame{}
+		err := wire.UnmarshalBE(&flapFrame, clientConn)
+		assert.NoError(t, err, "read FLAP frame %d", i)
+		assert.Equal(t, uint8(42), flapFrame.StartMarker)
+		assert.Equal(t, wire.FLAPFrameData, flapFrame.FrameType)
+
+		snac := wire.SNACFrame{}
+		buf := bytes.NewBuffer(flapFrame.Payload)
+		err = wire.UnmarshalBE(&snac, buf)
+		assert.NoError(t, err, "unmarshal SNAC %d", i)
+		assert.Equal(t, expected.Frame.FoodGroup, snac.FoodGroup)
+		assert.Equal(t, expected.Frame.SubGroup, snac.SubGroup)
+	}
+
+	_ = clientConn.Close()
+
+	select {
+	case err := <-doneServer:
+		assert.NoError(t, err)
+	case <-time.After(5 * time.Second):
+		t.Fatal("routeConnection did not exit in time")
+	}
+
+	signoutWG.Wait()
+}