connection_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package oscar
  2. import (
  3. "context"
  4. "io"
  5. "log/slog"
  6. "sync"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/mock"
  10. "github.com/mk6i/retro-aim-server/config"
  11. "github.com/mk6i/retro-aim-server/state"
  12. "github.com/mk6i/retro-aim-server/wire"
  13. )
  14. func TestHandleChatConnection_MessageRelay(t *testing.T) {
  15. sessionManager := state.NewInMemorySessionManager(slog.Default())
  16. // add a user to session that will receive relayed messages
  17. sess := sessionManager.AddSession("bob")
  18. // start the server connection handler in the background
  19. serverReader, _ := io.Pipe()
  20. clientReader, serverWriter := io.Pipe()
  21. go func() {
  22. flapc := wire.NewFlapClient(0, nil, serverWriter)
  23. err := dispatchIncomingMessages(context.Background(), sess, flapc, serverReader, slog.Default(), nil, config.Config{})
  24. assert.NoError(t, err)
  25. }()
  26. inboundMsgs := []wire.SNACMessage{
  27. {
  28. Frame: wire.SNACFrame{
  29. FoodGroup: wire.Chat,
  30. SubGroup: wire.ChatUsersJoined,
  31. },
  32. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  33. Users: []wire.TLVUserInfo{
  34. {
  35. ScreenName: "screenname1",
  36. },
  37. },
  38. },
  39. },
  40. {
  41. Frame: wire.SNACFrame{
  42. FoodGroup: wire.Chat,
  43. SubGroup: wire.ChatUsersLeft,
  44. },
  45. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  46. Users: []wire.TLVUserInfo{
  47. {
  48. ScreenName: "screenname2",
  49. },
  50. },
  51. },
  52. },
  53. }
  54. // relay messages to user session
  55. for _, msg := range inboundMsgs {
  56. sessionManager.RelayToScreenName(context.Background(), state.NewIdentScreenName("bob"), msg)
  57. }
  58. // consume and verify the relayed messages
  59. for i := 0; i < len(inboundMsgs); i++ {
  60. flap := wire.FLAPFrame{}
  61. assert.NoError(t, wire.Unmarshal(&flap, clientReader))
  62. snac, err := flap.ReadBody(clientReader)
  63. assert.NoError(t, err)
  64. frame := wire.SNACFrame{}
  65. assert.NoError(t, wire.Unmarshal(&frame, snac))
  66. assert.Equal(t, inboundMsgs[i].Frame, frame)
  67. body := wire.SNAC_0x0E_0x03_ChatUsersJoined{}
  68. assert.NoError(t, wire.Unmarshal(&body, snac))
  69. assert.Equal(t, inboundMsgs[i].Body, body)
  70. }
  71. // stop the session, which terminates the connection handler goroutine
  72. sess.Close()
  73. <-sess.Closed()
  74. // verify the connection handler sends client disconnection message before
  75. // terminating
  76. flap := wire.FLAPFrame{}
  77. assert.NoError(t, wire.Unmarshal(&flap, clientReader))
  78. assert.Equal(t, wire.FLAPFrameSignoff, flap.FrameType)
  79. }
  80. func TestHandleChatConnection_ClientRequest(t *testing.T) {
  81. sessionManager := state.NewInMemorySessionManager(slog.Default())
  82. // add session so that the function can terminate upon closure
  83. sess := sessionManager.AddSession("bob")
  84. inboundMsgs := []wire.SNACMessage{
  85. {
  86. Frame: wire.SNACFrame{
  87. FoodGroup: wire.Chat,
  88. SubGroup: wire.ChatUsersJoined,
  89. },
  90. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  91. Users: []wire.TLVUserInfo{
  92. {
  93. ScreenName: "screenname1",
  94. },
  95. },
  96. },
  97. },
  98. {
  99. Frame: wire.SNACFrame{
  100. FoodGroup: wire.Chat,
  101. SubGroup: wire.ChatUsersLeft,
  102. },
  103. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  104. Users: []wire.TLVUserInfo{
  105. {
  106. ScreenName: "screenname2",
  107. },
  108. },
  109. },
  110. },
  111. }
  112. wg := &sync.WaitGroup{}
  113. wg.Add(len(inboundMsgs))
  114. // set up mock handlers to receive messages and verify their contents
  115. router := newMockHandler(t)
  116. for _, msg := range inboundMsgs {
  117. msg := msg
  118. router.EXPECT().
  119. Handle(mock.Anything, sess, msg.Frame, mock.Anything, mock.Anything).
  120. Run(func(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) {
  121. defer wg.Done()
  122. body := wire.SNAC_0x0E_0x03_ChatUsersJoined{}
  123. assert.NoError(t, wire.Unmarshal(&body, r))
  124. assert.Equal(t, msg.Body, body)
  125. }).
  126. Return(nil)
  127. }
  128. // start the server connection handler in the background
  129. serverReader, clientWriter := io.Pipe()
  130. clientReader, serverWriter := io.Pipe()
  131. go func() {
  132. flapc := wire.NewFlapClient(0, nil, serverWriter)
  133. assert.NoError(t, dispatchIncomingMessages(context.Background(), sess, flapc, serverReader, slog.Default(), router, config.Config{}))
  134. }()
  135. // send client messages
  136. flapc := wire.NewFlapClient(0, nil, clientWriter)
  137. for _, msg := range inboundMsgs {
  138. err := flapc.SendSNAC(msg.Frame, msg.Body)
  139. assert.NoError(t, err)
  140. }
  141. wg.Wait()
  142. // stop the session, which terminates the connection handler goroutine
  143. sess.Close()
  144. <-sess.Closed()
  145. // verify the connection handler sends client disconnection message before
  146. // terminating
  147. flap := wire.FLAPFrame{}
  148. assert.NoError(t, wire.Unmarshal(&flap, clientReader))
  149. assert.Equal(t, wire.FLAPFrameSignoff, flap.FrameType)
  150. }