4
0

toc_integration_test.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "path/filepath"
  7. "regexp"
  8. "strings"
  9. "testing"
  10. "time"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/mk6i/open-oscar-server/wire"
  13. )
  14. // tocTestClient is a TOC client connected to the TOC server under test.
  15. type tocTestClient struct {
  16. t *testing.T
  17. name string
  18. conn net.Conn
  19. flapc *wire.FlapClient
  20. lines chan string
  21. }
  22. // dialTOC connects a TOC client to addr and sends the signon command.
  23. func dialTOC(t *testing.T, addr string, screenName string, signonCmd string) *tocTestClient {
  24. conn, err := net.Dial("tcp", addr)
  25. if !assert.NoError(t, err) {
  26. t.FailNow()
  27. }
  28. _, err = conn.Write([]byte("FLAPON\r\n\r\n"))
  29. assert.NoError(t, err)
  30. flapc := wire.NewFlapClient(0, conn, conn)
  31. _, err = flapc.ReceiveSignonFrame()
  32. assert.NoError(t, err)
  33. assert.NoError(t, flapc.SendSignonFrame([]wire.TLV{wire.NewTLVBE(0x01, screenName)}))
  34. c := &tocTestClient{
  35. t: t,
  36. name: screenName,
  37. conn: conn,
  38. flapc: flapc,
  39. lines: make(chan string, 100),
  40. }
  41. go func() {
  42. defer close(c.lines)
  43. for {
  44. frame, err := flapc.ReceiveFLAP()
  45. if err != nil {
  46. return
  47. }
  48. if frame.FrameType == wire.FLAPFrameData {
  49. c.lines <- string(frame.Payload)
  50. }
  51. }
  52. }()
  53. c.send(signonCmd)
  54. return c
  55. }
  56. func (c *tocTestClient) send(cmd string) {
  57. assert.NoError(c.t, c.flapc.SendDataFrame(append([]byte(cmd), 0)))
  58. }
  59. // expect skips lines until one matches pattern, failing the test if none does
  60. // within a few seconds. It returns the match and its submatches.
  61. func (c *tocTestClient) expect(pattern string) []string {
  62. rx := regexp.MustCompile(pattern)
  63. var skipped []string
  64. timeout := time.After(5 * time.Second)
  65. for {
  66. select {
  67. case line, ok := <-c.lines:
  68. if !ok {
  69. c.t.Fatalf("%s: connection closed waiting for /%s/, skipped %q", c.name, pattern, skipped)
  70. }
  71. if m := rx.FindStringSubmatch(line); m != nil {
  72. return m
  73. }
  74. skipped = append(skipped, line)
  75. case <-timeout:
  76. c.t.Fatalf("%s: timed out waiting for /%s/, skipped %q", c.name, pattern, skipped)
  77. }
  78. }
  79. }
  80. // TestTOC_OSCARIntegration drives TOC clients through the TOC server against a
  81. // real OSCAR server that TOC reaches over in-memory OSCAR connections.
  82. func TestTOC_OSCARIntegration(t *testing.T) {
  83. ln, err := net.Listen("tcp", "127.0.0.1:0")
  84. assert.NoError(t, err)
  85. tocAddr := ln.Addr().String()
  86. assert.NoError(t, ln.Close())
  87. t.Setenv("OSCAR_LISTENERS", "LOCAL://127.0.0.1:5190")
  88. t.Setenv("OSCAR_ADVERTISED_LISTENERS_PLAIN", "LOCAL://127.0.0.1:5190")
  89. t.Setenv("TOC_LISTENERS", tocAddr)
  90. t.Setenv("API_LISTENER", "127.0.0.1:0")
  91. t.Setenv("DB_PATH", filepath.Join(t.TempDir(), "oscar.sqlite"))
  92. t.Setenv("DISABLE_AUTH", "true")
  93. t.Setenv("LOG_LEVEL", "error")
  94. t.Setenv("WEBAPI_ENABLED", "false")
  95. t.Setenv("ICQ_LEGACY_ENABLED", "false")
  96. deps, err := MakeCommonDeps()
  97. if !assert.NoError(t, err) {
  98. t.FailNow()
  99. }
  100. oscarServer := OSCAR(deps)
  101. tocServer := TOC(deps, oscarServer)
  102. go func() {
  103. _ = tocServer.ListenAndServe()
  104. }()
  105. defer func() {
  106. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  107. defer cancel()
  108. _ = oscarServer.Shutdown(ctx)
  109. _ = tocServer.Shutdown(ctx)
  110. }()
  111. assert.Eventually(t, func() bool {
  112. conn, err := net.Dial("tcp", tocAddr)
  113. if err != nil {
  114. return false
  115. }
  116. _ = conn.Close()
  117. return true
  118. }, 5*time.Second, 10*time.Millisecond)
  119. const password = "0x2408105c23001130"
  120. alice := dialTOC(t, tocAddr, "alice", `toc2_login localhost 9898 alice `+password+` english "TIC:Test" 160 US "" "" 3 0 30303 -kentucky -utf8 103547776`)
  121. alice.expect(`^SIGN_ON:TOC2\.0$`)
  122. alice.expect(`^NICK:alice$`)
  123. alice.expect(`^CONFIG2:done:\n$`)
  124. alice.send("toc2_new_buddies {g:Buddies\nb:bob\n}")
  125. alice.expect(`^NEW_BUDDY_REPLY2:bob:added$`)
  126. alice.send("toc_init_done")
  127. bob := dialTOC(t, tocAddr, "bob", `toc_signon localhost 9898 bob `+password+` english "TIC:Test"`)
  128. bob.expect(`^SIGN_ON:TOC1\.0$`)
  129. bob.expect(`^CONFIG:$`)
  130. bob.expect(`^NICK:bob$`)
  131. bob.send("toc_add_buddy alice")
  132. bob.send("toc_init_done")
  133. // presence in both directions
  134. alice.expect(`^UPDATE_BUDDY2:bob:T:0:\d+:0:.{3}:$`)
  135. bob.expect(`^UPDATE_BUDDY:alice:T:0:\d+:0:.{3}$`)
  136. // instant messages in both directions
  137. bob.send(`toc_send_im alice "hello alice"`)
  138. alice.expect(`^IM_IN_ENC2:bob:F:F:T:...:F:L:en:hello alice$`)
  139. alice.send(`toc2_send_im bob "hello bob"`)
  140. bob.expect(`^IM_IN:alice:F:hello bob$`)
  141. // a reply that only makes sense next to its request
  142. bob.send("toc_get_status nobody")
  143. bob.expect(`^ERROR:901:nobody$`)
  144. // the room is announced before its first member list
  145. alice.send(`toc_chat_join 4 "Integration Room"`)
  146. alice.expect(`^CHAT_JOIN:0:Integration Room$`)
  147. alice.expect(`^CHAT_UPDATE_BUDDY:0:T:alice$`)
  148. alice.send(`toc_chat_send 0 "hello room"`)
  149. alice.expect(`^CHAT_IN_ENC:0:alice:F:A:en:hello room$`)
  150. // invitations, accepting them, and leaving rooms
  151. alice.send(`toc_chat_invite 0 "join us" bob`)
  152. invite := bob.expect(`^CHAT_INVITE:Integration Room:(\d+):alice:join us$`)
  153. bob.send("toc_chat_accept " + invite[1])
  154. bob.expect(`^CHAT_JOIN:` + invite[1] + `:Integration Room$`)
  155. alice.expect(`^CHAT_UPDATE_BUDDY:0:T:bob$`)
  156. bob.send("toc_chat_leave " + invite[1])
  157. bob.expect(`^CHAT_LEFT:` + invite[1] + `$`)
  158. alice.expect(`^CHAT_UPDATE_BUDDY:0:F:bob$`)
  159. // a large buddy list addition is stored in full
  160. var newBuddies strings.Builder
  161. for i := range 60 {
  162. fmt.Fprintf(&newBuddies, "g:Group %02d\nb:buddy%02d\n", i, i)
  163. }
  164. alice.send("toc2_new_buddies {" + newBuddies.String() + "}")
  165. for i := range 60 {
  166. alice.expect(fmt.Sprintf(`^NEW_BUDDY_REPLY2:buddy%02d:added$`, i))
  167. }
  168. // alice's commands are handled in order, so her buddy list is stored once
  169. // this reply arrives
  170. alice.send("toc_get_status alice")
  171. alice.expect(`^UPDATE_BUDDY2:alice:T:`)
  172. alice2 := dialTOC(t, tocAddr, "alice", `toc2_login localhost 9898 alice `+password+` english "TIC:Test" 160 US "" "" 3 0 30303 -kentucky -utf8 103547776`)
  173. config := alice2.expect(`^CONFIG2:(?s:.*)$`)[0]
  174. for i := range 60 {
  175. assert.Contains(t, config, fmt.Sprintf("g:Group %02d\nb:buddy%02d\n", i, i))
  176. }
  177. assert.NotContains(t, config, "b:\n")
  178. assert.NoError(t, alice2.conn.Close())
  179. // a TOC1 user's deny list hides them from the denied user
  180. carol := dialTOC(t, tocAddr, "carol", `toc_signon localhost 9898 carol `+password+` english "TIC:Test"`)
  181. carol.expect(`^NICK:carol$`)
  182. carol.send("toc_add_deny dave")
  183. carol.send("toc_init_done")
  184. // carol's commands are handled in order, so she's signed on once this
  185. // reply arrives
  186. carol.send("toc_get_status carol")
  187. carol.expect(`^UPDATE_BUDDY:carol:T:`)
  188. dave := dialTOC(t, tocAddr, "dave", `toc_signon localhost 9898 dave `+password+` english "TIC:Test"`)
  189. dave.expect(`^NICK:dave$`)
  190. dave.send("toc_init_done")
  191. dave.send("toc_get_status carol")
  192. dave.expect(`^ERROR:901:carol$`)
  193. bob.send("toc_get_status carol")
  194. bob.expect(`^UPDATE_BUDDY:carol:T:`)
  195. assert.NoError(t, carol.conn.Close())
  196. assert.NoError(t, dave.conn.Close())
  197. // signing off is seen by buddies
  198. assert.NoError(t, bob.conn.Close())
  199. alice.expect(`^UPDATE_BUDDY2:bob:F:0:0:0: :$`)
  200. assert.NoError(t, alice.conn.Close())
  201. }