server_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package toc
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "log/slog"
  7. "sync"
  8. "testing"
  9. "github.com/mk6i/retro-aim-server/state"
  10. "github.com/mk6i/retro-aim-server/wire"
  11. "github.com/stretchr/testify/assert"
  12. )
  13. // ensure correct behavior during global context cancellation (server shutdown)
  14. func TestServer_handleTOCRequest_serverShutdown(t *testing.T) {
  15. wg := sync.WaitGroup{}
  16. wg.Add(1)
  17. ctx, cancel := context.WithCancel(context.Background())
  18. go func() {
  19. defer wg.Done()
  20. sv := Server{
  21. bosProxy: testOSCARProxy(t),
  22. logger: slog.Default(),
  23. recalcWarning: func(ctx context.Context, sess *state.Session) error { return nil },
  24. lowerWarnLevel: func(ctx context.Context, sess *state.Session) {},
  25. }
  26. serverReader, _ := io.Pipe()
  27. fc := wire.NewFlapClient(0, serverReader, nil)
  28. closeConn := func() {
  29. _ = serverReader.Close()
  30. }
  31. sess := newTestSession("me")
  32. err := sv.handleTOCRequest(ctx, closeConn, sess, NewChatRegistry(), fc)
  33. assert.True(t, errors.Is(err, errTOCProcessing) || errors.Is(err, errServerWrite))
  34. }()
  35. // cancel context, simulating server shutdown
  36. cancel()
  37. // wait for handleTOCRequest to return
  38. wg.Wait()
  39. }
  40. // ensure correct behavior when client TCP connection disconnects
  41. func TestServer_handleTOCRequest_clientReadDisconnect(t *testing.T) {
  42. wg := sync.WaitGroup{}
  43. wg.Add(1)
  44. serverReader, _ := io.Pipe()
  45. go func() {
  46. defer wg.Done()
  47. closeConn := func() {
  48. _ = serverReader.Close()
  49. }
  50. sess := newTestSession("me")
  51. fc := wire.NewFlapClient(0, serverReader, nil)
  52. sv := Server{
  53. bosProxy: testOSCARProxy(t),
  54. logger: slog.Default(),
  55. recalcWarning: func(ctx context.Context, sess *state.Session) error { return nil },
  56. lowerWarnLevel: func(ctx context.Context, sess *state.Session) {},
  57. }
  58. err := sv.handleTOCRequest(context.Background(), closeConn, sess, NewChatRegistry(), fc)
  59. assert.ErrorIs(t, err, errClientReq)
  60. assert.ErrorIs(t, err, io.ErrClosedPipe)
  61. }()
  62. // simulate a client TCP disconnect
  63. _ = serverReader.Close()
  64. // wait for handleTOCRequest to return
  65. wg.Wait()
  66. }
  67. // ensure correct behavior when session gets closed by another login
  68. func TestServer_handleTOCRequest_sessClose(t *testing.T) {
  69. wg := sync.WaitGroup{}
  70. wg.Add(1)
  71. sess := newTestSession("me")
  72. go func() {
  73. defer wg.Done()
  74. serverReader, _ := io.Pipe()
  75. fc := wire.NewFlapClient(0, serverReader, nil)
  76. closeConn := func() {
  77. _ = serverReader.Close()
  78. }
  79. sv := Server{
  80. bosProxy: testOSCARProxy(t),
  81. logger: slog.Default(),
  82. recalcWarning: func(ctx context.Context, sess *state.Session) error { return nil },
  83. lowerWarnLevel: func(ctx context.Context, sess *state.Session) {},
  84. }
  85. err := sv.handleTOCRequest(context.Background(), closeConn, sess, NewChatRegistry(), fc)
  86. assert.ErrorIs(t, err, errTOCProcessing)
  87. assert.ErrorIs(t, err, errDisconnect)
  88. }()
  89. // close the session, simulating another client login kicking this session
  90. sess.Close()
  91. // wait for handleTOCRequest to return
  92. wg.Wait()
  93. }
  94. // ensure correct behavior when writing server response fails
  95. func TestServer_handleTOCRequest_replyFailure(t *testing.T) {
  96. wg := sync.WaitGroup{}
  97. wg.Add(1)
  98. serverReader, clientWriter := io.Pipe()
  99. clientReader, serverWriter := io.Pipe()
  100. go func() {
  101. defer wg.Done()
  102. closeConn := func() {
  103. _ = serverReader.Close()
  104. }
  105. sess := newTestSession("me")
  106. fc := wire.NewFlapClient(0, serverReader, serverWriter)
  107. sv := Server{
  108. bosProxy: testOSCARProxy(t),
  109. logger: slog.Default(),
  110. recalcWarning: func(ctx context.Context, sess *state.Session) error { return nil },
  111. lowerWarnLevel: func(ctx context.Context, sess *state.Session) {},
  112. }
  113. err := sv.handleTOCRequest(context.Background(), closeConn, sess, NewChatRegistry(), fc)
  114. assert.ErrorIs(t, err, errServerWrite)
  115. assert.ErrorIs(t, err, io.ErrClosedPipe)
  116. }()
  117. // simulate a failed TCP write
  118. _ = serverWriter.Close()
  119. // set up a TOC client
  120. fc := wire.NewFlapClient(0, clientReader, clientWriter)
  121. // send a TOC command
  122. err := fc.SendDataFrame([]byte(`toc_get_status`))
  123. assert.NoError(t, err)
  124. // wait for handleTOCRequest to return
  125. wg.Wait()
  126. }
  127. // ensure correct behavior when writing server response fails
  128. func TestServer_handleTOCRequest_happyPath(t *testing.T) {
  129. wg := sync.WaitGroup{}
  130. wg.Add(1)
  131. serverReader, clientWriter := io.Pipe()
  132. clientReader, serverWriter := io.Pipe()
  133. go func() {
  134. defer wg.Done()
  135. closeConn := func() {
  136. _ = serverReader.Close()
  137. }
  138. fc := wire.NewFlapClient(0, serverReader, serverWriter)
  139. sv := Server{
  140. bosProxy: testOSCARProxy(t),
  141. logger: slog.Default(),
  142. recalcWarning: func(ctx context.Context, sess *state.Session) error { return nil },
  143. lowerWarnLevel: func(ctx context.Context, sess *state.Session) {},
  144. }
  145. err := sv.handleTOCRequest(context.Background(), closeConn, newTestSession("me"), NewChatRegistry(), fc)
  146. assert.ErrorIs(t, err, errClientReq)
  147. assert.ErrorIs(t, err, io.ErrClosedPipe)
  148. }()
  149. // set up a TOC client
  150. fc := wire.NewFlapClient(0, clientReader, clientWriter)
  151. // send a malformed TOC command to the server
  152. err := fc.SendDataFrame([]byte(`toc_get_status`))
  153. assert.NoError(t, err)
  154. // wait for the TOC response from the server
  155. frame, err := fc.ReceiveFLAP()
  156. assert.NoError(t, err)
  157. // expecting an error from TOC because the command is malformed. this
  158. // demonstrates that a command was processed by the TOC handler.
  159. assert.Contains(t, string(frame.Payload), "internal server error")
  160. // cleanly disconnect
  161. _ = serverReader.Close()
  162. // wait for handleTOCRequest to return
  163. wg.Wait()
  164. }