cmd_server_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. package toc
  2. import (
  3. "context"
  4. "log/slog"
  5. "net"
  6. "sync"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/mock"
  10. "github.com/mk6i/open-oscar-server/state"
  11. "github.com/mk6i/open-oscar-server/wire"
  12. )
  13. func TestOSCARProxy_RecvBOS_ChatIn(t *testing.T) {
  14. cases := []struct {
  15. // name is the unit test name
  16. name string
  17. // me is the TOC user session
  18. me *state.SessionInstance
  19. // chatID is the chat ID
  20. chatID int
  21. // givenMsg is the incoming SNAC
  22. givenMsg wire.SNACMessage
  23. // wantCmd is the expected TOC response
  24. wantCmd []byte
  25. }{
  26. {
  27. name: "send chat message",
  28. me: newTestSession("me"),
  29. chatID: 0,
  30. givenMsg: wire.SNACMessage{
  31. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  32. TLVRestBlock: wire.TLVRestBlock{
  33. TLVList: wire.TLVList{
  34. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{
  35. ScreenName: "them",
  36. }),
  37. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  38. TLVList: wire.TLVList{
  39. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "<p>hello world!</p>"),
  40. },
  41. }),
  42. },
  43. },
  44. },
  45. },
  46. wantCmd: []byte("CHAT_IN:0:them:F:<p>hello world!</p>"),
  47. },
  48. }
  49. for _, tc := range cases {
  50. t.Run(tc.name, func(t *testing.T) {
  51. ctx, cancel := context.WithCancel(context.Background())
  52. svc := OSCARProxy{
  53. Logger: slog.Default(),
  54. }
  55. ch := make(chan []byte)
  56. wg := &sync.WaitGroup{}
  57. wg.Add(1)
  58. go func() {
  59. defer wg.Done()
  60. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  61. }()
  62. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  63. assert.Equal(t, state.SessSendOK, status)
  64. gotCmd := <-ch
  65. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  66. cancel()
  67. wg.Wait()
  68. })
  69. }
  70. }
  71. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyArrived(t *testing.T) {
  72. cases := []struct {
  73. // name is the unit test name
  74. name string
  75. // me is the TOC user session
  76. me *state.SessionInstance
  77. // chatID is the chat ID
  78. chatID int
  79. // givenMsg is the incoming SNAC
  80. givenMsg wire.SNACMessage
  81. // wantCmd is the expected TOC response
  82. wantCmd []byte
  83. }{
  84. {
  85. name: "send chat participant arrival",
  86. me: newTestSession("me"),
  87. givenMsg: wire.SNACMessage{
  88. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  89. Users: []wire.TLVUserInfo{
  90. {ScreenName: "user1"},
  91. {ScreenName: "user2"},
  92. },
  93. },
  94. },
  95. wantCmd: []byte("CHAT_UPDATE_BUDDY:0:T:user1:user2"),
  96. },
  97. }
  98. for _, tc := range cases {
  99. t.Run(tc.name, func(t *testing.T) {
  100. ctx, cancel := context.WithCancel(context.Background())
  101. svc := OSCARProxy{
  102. Logger: slog.Default(),
  103. }
  104. ch := make(chan []byte)
  105. wg := &sync.WaitGroup{}
  106. wg.Add(1)
  107. go func() {
  108. defer wg.Done()
  109. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  110. }()
  111. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  112. assert.Equal(t, state.SessSendOK, status)
  113. gotCmd := <-ch
  114. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  115. cancel()
  116. wg.Wait()
  117. })
  118. }
  119. }
  120. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyLeft(t *testing.T) {
  121. cases := []struct {
  122. // name is the unit test name
  123. name string
  124. // me is the TOC user session
  125. me *state.SessionInstance
  126. // chatID is the chat ID
  127. chatID int
  128. // givenMsg is the incoming SNAC
  129. givenMsg wire.SNACMessage
  130. // wantCmd is the expected TOC response
  131. wantCmd []byte
  132. }{
  133. {
  134. name: "send chat participant departure",
  135. me: newTestSession("me"),
  136. givenMsg: wire.SNACMessage{
  137. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{
  138. Users: []wire.TLVUserInfo{
  139. {ScreenName: "user1"},
  140. {ScreenName: "user2"},
  141. },
  142. },
  143. },
  144. wantCmd: []byte("CHAT_UPDATE_BUDDY:0:F:user1:user2"),
  145. },
  146. }
  147. for _, tc := range cases {
  148. t.Run(tc.name, func(t *testing.T) {
  149. ctx, cancel := context.WithCancel(context.Background())
  150. svc := OSCARProxy{
  151. Logger: slog.Default(),
  152. }
  153. ch := make(chan []byte)
  154. wg := &sync.WaitGroup{}
  155. wg.Add(1)
  156. go func() {
  157. defer wg.Done()
  158. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  159. }()
  160. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  161. assert.Equal(t, state.SessSendOK, status)
  162. gotCmd := <-ch
  163. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  164. cancel()
  165. wg.Wait()
  166. })
  167. }
  168. }
  169. func TestOSCARProxy_RecvBOS_Eviled(t *testing.T) {
  170. cases := []struct {
  171. // name is the unit test name
  172. name string
  173. // me is the TOC user session
  174. me *state.SessionInstance
  175. // givenMsg is the incoming SNAC
  176. givenMsg wire.SNACMessage
  177. // chatRegistry is the chat registry for the current session
  178. chatRegistry *ChatRegistry
  179. // wantCmd is the expected TOC response
  180. wantCmd []byte
  181. }{
  182. {
  183. name: "anonymous warning - 10%",
  184. me: newTestSession("me"),
  185. givenMsg: wire.SNACMessage{
  186. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  187. NewEvil: 100,
  188. },
  189. },
  190. wantCmd: []byte("EVILED:10:"),
  191. },
  192. {
  193. name: "normal warning - 10%",
  194. me: newTestSession("me"),
  195. givenMsg: wire.SNACMessage{
  196. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  197. NewEvil: 100,
  198. Snitcher: &struct {
  199. wire.TLVUserInfo
  200. }{
  201. TLVUserInfo: wire.TLVUserInfo{
  202. ScreenName: "them",
  203. },
  204. },
  205. },
  206. },
  207. wantCmd: []byte("EVILED:10:them"),
  208. },
  209. }
  210. for _, tc := range cases {
  211. t.Run(tc.name, func(t *testing.T) {
  212. ctx, cancel := context.WithCancel(context.Background())
  213. svc := testOSCARProxy(t)
  214. ch := make(chan []byte)
  215. wg := &sync.WaitGroup{}
  216. wg.Add(1)
  217. go func() {
  218. defer wg.Done()
  219. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  220. assert.NoError(t, err)
  221. }()
  222. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  223. assert.Equal(t, state.SessSendOK, status)
  224. gotCmd := <-ch
  225. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  226. cancel()
  227. wg.Wait()
  228. })
  229. }
  230. }
  231. func TestOSCARProxy_RecvBOS_IMIn(t *testing.T) {
  232. cases := []struct {
  233. // name is the unit test name
  234. name string
  235. // me is the TOC user session
  236. me *state.SessionInstance
  237. // givenMsg is the incoming SNAC
  238. givenMsg wire.SNACMessage
  239. // wantCmd is the expected TOC response
  240. wantCmd []byte
  241. }{
  242. {
  243. name: "send IM",
  244. me: newTestSession("me"),
  245. givenMsg: wire.SNACMessage{
  246. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  247. ChannelID: wire.ICBMChannelIM,
  248. TLVUserInfo: wire.TLVUserInfo{
  249. ScreenName: "them",
  250. },
  251. TLVRestBlock: wire.TLVRestBlock{
  252. TLVList: wire.TLVList{
  253. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  254. {
  255. ID: 0x5,
  256. Version: 0x1,
  257. Payload: []uint8{0x1, 0x1, 0x2},
  258. },
  259. {
  260. ID: 0x1,
  261. Version: 0x1,
  262. Payload: []uint8{
  263. 0x0, 0x0, // charset
  264. 0x0, 0x0, // lang
  265. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  266. },
  267. },
  268. }),
  269. },
  270. },
  271. },
  272. },
  273. wantCmd: []byte("IM_IN:them:F:hello world!"),
  274. },
  275. {
  276. name: "send IM - auto-response",
  277. me: newTestSession("me"),
  278. givenMsg: wire.SNACMessage{
  279. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  280. ChannelID: wire.ICBMChannelIM,
  281. TLVUserInfo: wire.TLVUserInfo{
  282. ScreenName: "them",
  283. },
  284. TLVRestBlock: wire.TLVRestBlock{
  285. TLVList: wire.TLVList{
  286. wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}),
  287. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  288. {
  289. ID: 0x5,
  290. Version: 0x1,
  291. Payload: []uint8{0x1, 0x1, 0x2},
  292. },
  293. {
  294. ID: 0x1,
  295. Version: 0x1,
  296. Payload: []uint8{
  297. 0x0, 0x0, // charset
  298. 0x0, 0x0, // lang
  299. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  300. },
  301. },
  302. }),
  303. },
  304. },
  305. },
  306. },
  307. wantCmd: []byte("IM_IN:them:T:hello world!"),
  308. },
  309. {
  310. name: "send chat invitation",
  311. me: newTestSession("me"),
  312. givenMsg: wire.SNACMessage{
  313. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  314. ChannelID: wire.ICBMChannelRendezvous,
  315. TLVUserInfo: wire.TLVUserInfo{
  316. ScreenName: "them",
  317. },
  318. TLVRestBlock: wire.TLVRestBlock{
  319. TLVList: wire.TLVList{
  320. wire.NewTLVBE(wire.ICBMTLVData, []wire.ICBMCh2Fragment{
  321. {
  322. Capability: wire.CapChat,
  323. TLVRestBlock: wire.TLVRestBlock{
  324. TLVList: wire.TLVList{
  325. wire.NewTLVBE(wire.ICBMRdvTLVTagsInvitation, "join my chat!"),
  326. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, wire.ICBMRoomInfo{
  327. Cookie: "a-b-the room",
  328. }),
  329. },
  330. },
  331. },
  332. }),
  333. },
  334. },
  335. },
  336. },
  337. wantCmd: []byte("CHAT_INVITE:the room:0:them:join my chat!"),
  338. },
  339. {
  340. name: "receive file transfer rendezvous IM",
  341. me: newTestSession("me"),
  342. givenMsg: wire.SNACMessage{
  343. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  344. ChannelID: wire.ICBMChannelRendezvous,
  345. TLVUserInfo: newTestSession("them").Session().TLVUserInfo(),
  346. TLVRestBlock: wire.TLVRestBlock{
  347. TLVList: wire.TLVList{
  348. wire.NewTLVBE(wire.ICBMTLVWantEvents, []byte{}),
  349. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  350. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  351. Type: wire.ICBMRdvMessagePropose,
  352. Capability: wire.CapFileTransfer,
  353. TLVRestBlock: wire.TLVRestBlock{
  354. TLVList: wire.TLVList{
  355. wire.NewTLVBE(wire.ICBMRdvTLVTagsSeqNum, uint16(1)),
  356. wire.NewTLVBE(wire.ICBMRdvTLVTagsPort, uint16(4000)),
  357. wire.NewTLVBE(wire.ICBMRdvTLVTagsRdvIP, net.ParseIP("129.168.0.1").To4()),
  358. wire.NewTLVBE(wire.ICBMRdvTLVTagsRequesterIP, net.ParseIP("129.168.0.2").To4()),
  359. wire.NewTLVBE(wire.ICBMRdvTLVTagsVerifiedIP, net.ParseIP("129.168.0.3").To4()),
  360. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, []byte{'l', 'o', 'l'}),
  361. },
  362. },
  363. }),
  364. },
  365. },
  366. },
  367. },
  368. wantCmd: []byte("RVOUS_PROPOSE:them:09461343-4C7F-11D1-8222-444553540000:aGFoYWhhaGE=:1:129.168.0.1:129.168.0.2:129.168.0.3:4000:10001:bG9s"),
  369. },
  370. }
  371. for _, tc := range cases {
  372. t.Run(tc.name, func(t *testing.T) {
  373. ctx, cancel := context.WithCancel(context.Background())
  374. svc := testOSCARProxy(t)
  375. ch := make(chan []byte)
  376. wg := &sync.WaitGroup{}
  377. wg.Add(1)
  378. go func() {
  379. defer wg.Done()
  380. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  381. assert.NoError(t, err)
  382. }()
  383. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  384. assert.Equal(t, state.SessSendOK, status)
  385. gotCmd := <-ch
  386. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  387. cancel()
  388. wg.Wait()
  389. })
  390. }
  391. }
  392. func TestOSCARProxy_RecvBOS_UpdateBuddyArrival(t *testing.T) {
  393. cases := []struct {
  394. // name is the unit test name
  395. name string
  396. // me is the TOC user session
  397. me *state.SessionInstance
  398. // givenMsg is the incoming SNAC
  399. givenMsg wire.SNACMessage
  400. // wantCmd is the expected TOC response
  401. wantCmd []byte
  402. }{
  403. {
  404. name: "send buddy arrival - buddy online",
  405. me: newTestSession("me"),
  406. givenMsg: wire.SNACMessage{
  407. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  408. TLVUserInfo: wire.TLVUserInfo{
  409. ScreenName: "me",
  410. WarningLevel: 0,
  411. TLVBlock: wire.TLVBlock{
  412. TLVList: wire.TLVList{
  413. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  414. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  415. },
  416. },
  417. },
  418. },
  419. },
  420. wantCmd: []byte("UPDATE_BUDDY:me:T:0:1234:5678: O "),
  421. },
  422. {
  423. name: "send buddy arrival - buddy warned 10%",
  424. me: newTestSession("me"),
  425. givenMsg: wire.SNACMessage{
  426. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  427. TLVUserInfo: wire.TLVUserInfo{
  428. ScreenName: "me",
  429. WarningLevel: 100,
  430. TLVBlock: wire.TLVBlock{
  431. TLVList: wire.TLVList{
  432. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  433. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  434. },
  435. },
  436. },
  437. },
  438. },
  439. wantCmd: []byte("UPDATE_BUDDY:me:T:10:1234:5678: O "),
  440. },
  441. {
  442. name: "send buddy arrival - buddy away",
  443. me: newTestSession("me"),
  444. givenMsg: wire.SNACMessage{
  445. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  446. TLVUserInfo: wire.TLVUserInfo{
  447. ScreenName: "me",
  448. WarningLevel: 0,
  449. TLVBlock: wire.TLVBlock{
  450. TLVList: wire.TLVList{
  451. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  452. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  453. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagUnavailable),
  454. },
  455. },
  456. },
  457. },
  458. },
  459. wantCmd: []byte("UPDATE_BUDDY:me:T:0:1234:5678: OU"),
  460. },
  461. }
  462. for _, tc := range cases {
  463. t.Run(tc.name, func(t *testing.T) {
  464. ctx, cancel := context.WithCancel(context.Background())
  465. svc := testOSCARProxy(t)
  466. ch := make(chan []byte)
  467. wg := &sync.WaitGroup{}
  468. wg.Add(1)
  469. go func() {
  470. defer wg.Done()
  471. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  472. assert.NoError(t, err)
  473. }()
  474. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  475. assert.Equal(t, state.SessSendOK, status)
  476. gotCmd := <-ch
  477. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  478. cancel()
  479. wg.Wait()
  480. })
  481. }
  482. }
  483. func TestOSCARProxy_RecvBOS_UpdateBuddyDeparted(t *testing.T) {
  484. cases := []struct {
  485. // name is the unit test name
  486. name string
  487. // me is the TOC user session
  488. me *state.SessionInstance
  489. // givenMsg is the incoming SNAC
  490. givenMsg wire.SNACMessage
  491. // wantCmd is the expected TOC response
  492. wantCmd []byte
  493. }{
  494. {
  495. name: "send buddy departure",
  496. me: newTestSession("me"),
  497. givenMsg: wire.SNACMessage{
  498. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{
  499. TLVUserInfo: wire.TLVUserInfo{
  500. ScreenName: "me",
  501. },
  502. },
  503. },
  504. wantCmd: []byte("UPDATE_BUDDY:me:F:0:0:0: "),
  505. },
  506. }
  507. for _, tc := range cases {
  508. t.Run(tc.name, func(t *testing.T) {
  509. ctx, cancel := context.WithCancel(context.Background())
  510. svc := testOSCARProxy(t)
  511. ch := make(chan []byte)
  512. wg := &sync.WaitGroup{}
  513. wg.Add(1)
  514. go func() {
  515. defer wg.Done()
  516. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  517. assert.NoError(t, err)
  518. }()
  519. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  520. assert.Equal(t, state.SessSendOK, status)
  521. gotCmd := <-ch
  522. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  523. cancel()
  524. wg.Wait()
  525. })
  526. }
  527. }
  528. func TestOSCARProxy_RecvBOS_Signout(t *testing.T) {
  529. }
  530. func testOSCARProxy(t *testing.T) OSCARProxy {
  531. buddyService := newMockBuddyService(t)
  532. buddyService.EXPECT().
  533. BroadcastBuddyDeparted(mock.Anything, mock.Anything).
  534. Maybe().
  535. Return(nil)
  536. buddyListRegistry := newMockBuddyListRegistry(t)
  537. buddyListRegistry.EXPECT().
  538. UnregisterBuddyList(mock.Anything, mock.Anything).
  539. Maybe().
  540. Return(nil)
  541. authService := newMockAuthService(t)
  542. authService.EXPECT().
  543. Signout(mock.Anything, mock.Anything).
  544. Maybe()
  545. authService.EXPECT().
  546. SignoutChat(mock.Anything, mock.Anything).
  547. Maybe()
  548. return OSCARProxy{
  549. AuthService: authService,
  550. BuddyListRegistry: buddyListRegistry,
  551. BuddyService: buddyService,
  552. Logger: slog.Default(),
  553. }
  554. }