server_test.go 4.6 KB

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