server_test.go 5.1 KB

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