oservice_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. package handler
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/mk6i/retro-aim-server/config"
  6. "github.com/stretchr/testify/mock"
  7. "github.com/mk6i/retro-aim-server/oscar"
  8. "github.com/mk6i/retro-aim-server/server"
  9. "github.com/mk6i/retro-aim-server/state"
  10. "github.com/stretchr/testify/assert"
  11. )
  12. func TestReceiveAndSendServiceRequest(t *testing.T) {
  13. cases := []struct {
  14. // name is the unit test name
  15. name string
  16. // config is the application config
  17. cfg config.Config
  18. // chatRoom is the chat room the user connects to
  19. chatRoom *state.ChatRoom
  20. // userSession is the session of the user requesting the chat service
  21. // info
  22. userSession *state.Session
  23. // inputSNAC is the SNAC sent by the sender client
  24. inputSNAC oscar.SNACMessage
  25. // expectSNACFrame is the SNAC frame sent from the server to the recipient
  26. // client
  27. expectOutput oscar.SNACMessage
  28. // expectErr is the expected error returned by the router
  29. expectErr error
  30. }{
  31. {
  32. name: "request info for ICBM service, return invalid SNAC err",
  33. userSession: newTestSession("user_screen_name"),
  34. inputSNAC: oscar.SNACMessage{
  35. Frame: oscar.SNACFrame{
  36. RequestID: 1234,
  37. },
  38. Body: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  39. FoodGroup: oscar.ICBM,
  40. },
  41. },
  42. expectErr: server.ErrUnsupportedSubGroup,
  43. },
  44. {
  45. name: "request info for connecting to chat room, return chat service and chat room metadata",
  46. cfg: config.Config{
  47. OSCARHost: "127.0.0.1",
  48. ChatPort: 1234,
  49. },
  50. chatRoom: &state.ChatRoom{
  51. CreateTime: time.UnixMilli(0),
  52. DetailLevel: 4,
  53. Exchange: 8,
  54. Cookie: "the-chat-cookie",
  55. InstanceNumber: 16,
  56. Name: "my new chat",
  57. },
  58. userSession: newTestSession("user_screen_name", sessOptCannedID),
  59. inputSNAC: oscar.SNACMessage{
  60. Frame: oscar.SNACFrame{
  61. RequestID: 1234,
  62. },
  63. Body: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  64. FoodGroup: oscar.Chat,
  65. TLVRestBlock: oscar.TLVRestBlock{
  66. TLVList: oscar.TLVList{
  67. oscar.NewTLV(0x01, oscar.SNAC_0x01_0x04_TLVRoomInfo{
  68. Exchange: 8,
  69. Cookie: []byte("the-chat-cookie"),
  70. InstanceNumber: 16,
  71. }),
  72. },
  73. },
  74. },
  75. },
  76. expectOutput: oscar.SNACMessage{
  77. Frame: oscar.SNACFrame{
  78. FoodGroup: oscar.OService,
  79. SubGroup: oscar.OServiceServiceResponse,
  80. RequestID: 1234,
  81. },
  82. Body: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
  83. TLVRestBlock: oscar.TLVRestBlock{
  84. TLVList: oscar.TLVList{
  85. oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  86. oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, server.ChatCookie{
  87. Cookie: []byte("the-chat-cookie"),
  88. SessID: "user-userSession-id",
  89. }),
  90. oscar.NewTLV(oscar.OServiceTLVTagsGroupID, oscar.Chat),
  91. oscar.NewTLV(oscar.OServiceTLVTagsSSLCertName, ""),
  92. oscar.NewTLV(oscar.OServiceTLVTagsSSLState, uint8(0x00)),
  93. },
  94. },
  95. },
  96. },
  97. },
  98. {
  99. name: "request info for connecting to non-existent chat room, return SNAC error",
  100. cfg: config.Config{
  101. OSCARHost: "127.0.0.1",
  102. ChatPort: 1234,
  103. },
  104. chatRoom: nil,
  105. userSession: newTestSession("user_screen_name", sessOptCannedID),
  106. inputSNAC: oscar.SNACMessage{
  107. Frame: oscar.SNACFrame{
  108. RequestID: 1234,
  109. },
  110. Body: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  111. FoodGroup: oscar.Chat,
  112. TLVRestBlock: oscar.TLVRestBlock{
  113. TLVList: oscar.TLVList{
  114. oscar.NewTLV(0x01, oscar.SNAC_0x01_0x04_TLVRoomInfo{
  115. Exchange: 8,
  116. Cookie: []byte("the-chat-cookie"),
  117. InstanceNumber: 16,
  118. }),
  119. },
  120. },
  121. },
  122. },
  123. expectErr: server.ErrUnsupportedSubGroup,
  124. },
  125. }
  126. for _, tc := range cases {
  127. t.Run(tc.name, func(t *testing.T) {
  128. //
  129. // initialize dependencies
  130. //
  131. sessionManager := newMockSessionManager(t)
  132. chatRegistry := state.NewChatRegistry()
  133. if tc.chatRoom != nil {
  134. sessionManager.EXPECT().
  135. AddSession(tc.userSession.ID(), tc.userSession.ScreenName()).
  136. Return(&state.Session{}).
  137. Maybe()
  138. chatRegistry.Register(*tc.chatRoom, sessionManager)
  139. }
  140. //
  141. // send input SNAC
  142. //
  143. svc := NewOServiceServiceForBOS(OServiceService{
  144. cfg: tc.cfg,
  145. }, chatRegistry)
  146. outputSNAC, err := svc.ServiceRequestHandler(nil, tc.userSession, tc.inputSNAC.Frame,
  147. tc.inputSNAC.Body.(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
  148. assert.ErrorIs(t, err, tc.expectErr)
  149. if tc.expectErr != nil {
  150. return
  151. }
  152. //
  153. // verify output
  154. //
  155. assert.Equal(t, tc.expectOutput, outputSNAC)
  156. })
  157. }
  158. }
  159. func TestSetUserInfoFieldsHandler(t *testing.T) {
  160. cases := []struct {
  161. // name is the unit test name
  162. name string
  163. // userSession is the session of the user whose info is being set
  164. userSession *state.Session
  165. // inputSNAC is the SNAC sent from the client to the server
  166. inputSNAC oscar.SNACMessage
  167. // expectOutput is the SNAC reply sent from the server back to the
  168. // client
  169. expectOutput oscar.SNACMessage
  170. // broadcastMessage is the arrival/departure message sent to buddies
  171. broadcastMessage []struct {
  172. recipients []string
  173. msg oscar.SNACMessage
  174. }
  175. // interestedUserLookups contains all the users who have this user on
  176. // their buddy list
  177. interestedUserLookups map[string][]string
  178. // expectErr is the expected error returned
  179. expectErr error
  180. }{
  181. {
  182. name: "set user status to visible",
  183. userSession: newTestSession("user_screen_name"),
  184. inputSNAC: oscar.SNACMessage{
  185. Frame: oscar.SNACFrame{
  186. RequestID: 1234,
  187. },
  188. Body: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  189. TLVRestBlock: oscar.TLVRestBlock{
  190. TLVList: oscar.TLVList{
  191. oscar.NewTLV(oscar.OServiceUserInfoStatus, uint32(0x0000)),
  192. },
  193. },
  194. },
  195. },
  196. expectOutput: oscar.SNACMessage{
  197. Frame: oscar.SNACFrame{
  198. FoodGroup: oscar.OService,
  199. SubGroup: oscar.OServiceUserInfoUpdate,
  200. RequestID: 1234,
  201. },
  202. Body: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  203. TLVUserInfo: newTestSession("user_screen_name").TLVUserInfo(),
  204. },
  205. },
  206. broadcastMessage: []struct {
  207. recipients []string
  208. msg oscar.SNACMessage
  209. }{
  210. {
  211. recipients: []string{"friend1", "friend2"},
  212. msg: oscar.SNACMessage{
  213. Frame: oscar.SNACFrame{
  214. FoodGroup: oscar.Buddy,
  215. SubGroup: oscar.BuddyArrived,
  216. },
  217. Body: oscar.SNAC_0x03_0x0B_BuddyArrived{
  218. TLVUserInfo: newTestSession("user_screen_name").TLVUserInfo(),
  219. },
  220. },
  221. },
  222. },
  223. interestedUserLookups: map[string][]string{
  224. "user_screen_name": {"friend1", "friend2"},
  225. },
  226. },
  227. {
  228. name: "set user status to invisible",
  229. userSession: newTestSession("user_screen_name"),
  230. inputSNAC: oscar.SNACMessage{
  231. Frame: oscar.SNACFrame{
  232. RequestID: 1234,
  233. },
  234. Body: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  235. TLVRestBlock: oscar.TLVRestBlock{
  236. TLVList: oscar.TLVList{
  237. oscar.NewTLV(oscar.OServiceUserInfoStatus, uint32(0x0100)),
  238. },
  239. },
  240. },
  241. },
  242. expectOutput: oscar.SNACMessage{
  243. Frame: oscar.SNACFrame{
  244. FoodGroup: oscar.OService,
  245. SubGroup: oscar.OServiceUserInfoUpdate,
  246. RequestID: 1234,
  247. },
  248. Body: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  249. TLVUserInfo: newTestSession("user_screen_name", sessOptInvisible).TLVUserInfo(),
  250. },
  251. },
  252. broadcastMessage: []struct {
  253. recipients []string
  254. msg oscar.SNACMessage
  255. }{
  256. {
  257. recipients: []string{"friend1", "friend2"},
  258. msg: oscar.SNACMessage{
  259. Frame: oscar.SNACFrame{
  260. FoodGroup: oscar.Buddy,
  261. SubGroup: oscar.BuddyDeparted,
  262. },
  263. Body: oscar.SNAC_0x03_0x0C_BuddyDeparted{
  264. TLVUserInfo: oscar.TLVUserInfo{
  265. ScreenName: "user_screen_name",
  266. WarningLevel: 0,
  267. },
  268. },
  269. },
  270. },
  271. },
  272. interestedUserLookups: map[string][]string{
  273. "user_screen_name": {"friend1", "friend2"},
  274. },
  275. },
  276. }
  277. for _, tc := range cases {
  278. t.Run(tc.name, func(t *testing.T) {
  279. //
  280. // initialize dependencies
  281. //
  282. feedbagManager := newMockFeedbagManager(t)
  283. for user, friends := range tc.interestedUserLookups {
  284. feedbagManager.EXPECT().
  285. AdjacentUsers(user).
  286. Return(friends, nil).
  287. Maybe()
  288. }
  289. messageRelayer := newMockMessageRelayer(t)
  290. for _, broadcastMsg := range tc.broadcastMessage {
  291. messageRelayer.EXPECT().RelayToScreenNames(mock.Anything, broadcastMsg.recipients, broadcastMsg.msg)
  292. }
  293. //
  294. // send input SNAC
  295. //
  296. svc := NewOServiceService(config.Config{}, messageRelayer, feedbagManager)
  297. outputSNAC, err := svc.SetUserInfoFieldsHandler(nil, tc.userSession, tc.inputSNAC.Frame,
  298. tc.inputSNAC.Body.(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
  299. assert.ErrorIs(t, err, tc.expectErr)
  300. if tc.expectErr != nil {
  301. return
  302. }
  303. //
  304. // verify output
  305. //
  306. assert.Equal(t, tc.expectOutput, outputSNAC)
  307. })
  308. }
  309. }
  310. func TestOServiceService_RateParamsQueryHandler(t *testing.T) {
  311. svc := NewOServiceService(config.Config{}, nil, nil)
  312. have := svc.RateParamsQueryHandler(nil, oscar.SNACFrame{RequestID: 1234})
  313. want := oscar.SNACMessage{
  314. Frame: oscar.SNACFrame{
  315. FoodGroup: oscar.OService,
  316. SubGroup: oscar.OServiceRateParamsReply,
  317. RequestID: 1234,
  318. },
  319. Body: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
  320. RateClasses: []struct {
  321. ID uint16
  322. WindowSize uint32
  323. ClearLevel uint32
  324. AlertLevel uint32
  325. LimitLevel uint32
  326. DisconnectLevel uint32
  327. CurrentLevel uint32
  328. MaxLevel uint32
  329. LastTime uint32
  330. CurrentState uint8
  331. }{
  332. {
  333. ID: 0x0001,
  334. WindowSize: 0x00000050,
  335. ClearLevel: 0x000009C4,
  336. AlertLevel: 0x000007D0,
  337. LimitLevel: 0x000005DC,
  338. DisconnectLevel: 0x00000320,
  339. CurrentLevel: 0x00000D69,
  340. MaxLevel: 0x00001770,
  341. LastTime: 0x00000000,
  342. CurrentState: 0x00,
  343. },
  344. },
  345. RateGroups: []struct {
  346. ID uint16
  347. Pairs []struct {
  348. FoodGroup uint16
  349. SubGroup uint16
  350. } `count_prefix:"uint16"`
  351. }{
  352. {
  353. ID: 1,
  354. Pairs: []struct {
  355. FoodGroup uint16
  356. SubGroup uint16
  357. }{
  358. {
  359. FoodGroup: oscar.Buddy,
  360. SubGroup: oscar.BuddyRightsQuery,
  361. },
  362. {
  363. FoodGroup: oscar.Chat,
  364. SubGroup: oscar.ChatChannelMsgToHost,
  365. },
  366. {
  367. FoodGroup: oscar.ChatNav,
  368. SubGroup: oscar.ChatNavRequestChatRights,
  369. },
  370. {
  371. FoodGroup: oscar.ChatNav,
  372. SubGroup: oscar.ChatNavRequestRoomInfo,
  373. },
  374. {
  375. FoodGroup: oscar.ChatNav,
  376. SubGroup: oscar.ChatNavCreateRoom,
  377. },
  378. {
  379. FoodGroup: oscar.Feedbag,
  380. SubGroup: oscar.FeedbagRightsQuery,
  381. },
  382. {
  383. FoodGroup: oscar.Feedbag,
  384. SubGroup: oscar.FeedbagQuery,
  385. },
  386. {
  387. FoodGroup: oscar.Feedbag,
  388. SubGroup: oscar.FeedbagQueryIfModified,
  389. },
  390. {
  391. FoodGroup: oscar.Feedbag,
  392. SubGroup: oscar.FeedbagUse,
  393. },
  394. {
  395. FoodGroup: oscar.Feedbag,
  396. SubGroup: oscar.FeedbagInsertItem,
  397. },
  398. {
  399. FoodGroup: oscar.Feedbag,
  400. SubGroup: oscar.FeedbagUpdateItem,
  401. },
  402. {
  403. FoodGroup: oscar.Feedbag,
  404. SubGroup: oscar.FeedbagDeleteItem,
  405. },
  406. {
  407. FoodGroup: oscar.Feedbag,
  408. SubGroup: oscar.FeedbagStartCluster,
  409. },
  410. {
  411. FoodGroup: oscar.Feedbag,
  412. SubGroup: oscar.FeedbagEndCluster,
  413. },
  414. {
  415. FoodGroup: oscar.ICBM,
  416. SubGroup: oscar.ICBMAddParameters,
  417. },
  418. {
  419. FoodGroup: oscar.ICBM,
  420. SubGroup: oscar.ICBMParameterQuery,
  421. },
  422. {
  423. FoodGroup: oscar.ICBM,
  424. SubGroup: oscar.ICBMChannelMsgToHost,
  425. },
  426. {
  427. FoodGroup: oscar.ICBM,
  428. SubGroup: oscar.ICBMEvilRequest,
  429. },
  430. {
  431. FoodGroup: oscar.ICBM,
  432. SubGroup: oscar.ICBMClientErr,
  433. },
  434. {
  435. FoodGroup: oscar.ICBM,
  436. SubGroup: oscar.ICBMClientEvent,
  437. },
  438. {
  439. FoodGroup: oscar.Locate,
  440. SubGroup: oscar.LocateRightsQuery,
  441. },
  442. {
  443. FoodGroup: oscar.Locate,
  444. SubGroup: oscar.LocateSetInfo,
  445. },
  446. {
  447. FoodGroup: oscar.Locate,
  448. SubGroup: oscar.LocateSetDirInfo,
  449. },
  450. {
  451. FoodGroup: oscar.Locate,
  452. SubGroup: oscar.LocateGetDirInfo,
  453. },
  454. {
  455. FoodGroup: oscar.Locate,
  456. SubGroup: oscar.LocateSetKeywordInfo,
  457. },
  458. {
  459. FoodGroup: oscar.Locate,
  460. SubGroup: oscar.LocateUserInfoQuery2,
  461. },
  462. {
  463. FoodGroup: oscar.OService,
  464. SubGroup: oscar.OServiceServiceRequest,
  465. },
  466. {
  467. FoodGroup: oscar.OService,
  468. SubGroup: oscar.OServiceClientOnline,
  469. },
  470. {
  471. FoodGroup: oscar.OService,
  472. SubGroup: oscar.OServiceRateParamsQuery,
  473. },
  474. {
  475. FoodGroup: oscar.OService,
  476. SubGroup: oscar.OServiceRateParamsSubAdd,
  477. },
  478. {
  479. FoodGroup: oscar.OService,
  480. SubGroup: oscar.OServiceUserInfoQuery,
  481. },
  482. {
  483. FoodGroup: oscar.OService,
  484. SubGroup: oscar.OServiceIdleNotification,
  485. },
  486. {
  487. FoodGroup: oscar.OService,
  488. SubGroup: oscar.OServiceClientVersions,
  489. },
  490. {
  491. FoodGroup: oscar.OService,
  492. SubGroup: oscar.OServiceSetUserInfoFields,
  493. },
  494. },
  495. },
  496. },
  497. },
  498. }
  499. assert.Equal(t, want, have)
  500. }
  501. func TestOServiceServiceForBOS_WriteOServiceHostOnline(t *testing.T) {
  502. svc := NewOServiceServiceForBOS(*NewOServiceService(config.Config{}, nil, nil), nil)
  503. want := oscar.SNACMessage{
  504. Frame: oscar.SNACFrame{
  505. FoodGroup: oscar.OService,
  506. SubGroup: oscar.OServiceHostOnline,
  507. },
  508. Body: oscar.SNAC_0x01_0x03_OServiceHostOnline{
  509. FoodGroups: []uint16{
  510. oscar.Alert,
  511. oscar.Buddy,
  512. oscar.ChatNav,
  513. oscar.Feedbag,
  514. oscar.ICBM,
  515. oscar.Locate,
  516. oscar.OService,
  517. },
  518. },
  519. }
  520. have := svc.WriteOServiceHostOnline()
  521. assert.Equal(t, want, have)
  522. }
  523. func TestOServiceServiceForChat_WriteOServiceHostOnline(t *testing.T) {
  524. svc := NewOServiceServiceForChat(*NewOServiceService(config.Config{}, nil, nil), nil)
  525. want := oscar.SNACMessage{
  526. Frame: oscar.SNACFrame{
  527. FoodGroup: oscar.OService,
  528. SubGroup: oscar.OServiceHostOnline,
  529. },
  530. Body: oscar.SNAC_0x01_0x03_OServiceHostOnline{
  531. FoodGroups: []uint16{
  532. oscar.OService,
  533. oscar.Chat,
  534. },
  535. },
  536. }
  537. have := svc.WriteOServiceHostOnline()
  538. assert.Equal(t, want, have)
  539. }
  540. func TestOServiceService_ClientVersionsHandler(t *testing.T) {
  541. svc := NewOServiceService(config.Config{}, nil, nil)
  542. want := oscar.SNACMessage{
  543. Frame: oscar.SNACFrame{
  544. FoodGroup: oscar.OService,
  545. SubGroup: oscar.OServiceHostVersions,
  546. RequestID: 1234,
  547. },
  548. Body: oscar.SNAC_0x01_0x18_OServiceHostVersions{
  549. Versions: []uint16{5, 6, 7, 8},
  550. },
  551. }
  552. have := svc.ClientVersionsHandler(nil, oscar.SNACFrame{
  553. RequestID: 1234,
  554. }, oscar.SNAC_0x01_0x17_OServiceClientVersions{
  555. Versions: []uint16{5, 6, 7, 8},
  556. })
  557. assert.Equal(t, want, have)
  558. }
  559. func TestOServiceService_UserInfoQueryHandler(t *testing.T) {
  560. svc := NewOServiceService(config.Config{}, nil, nil)
  561. sess := newTestSession("test-user")
  562. want := oscar.SNACMessage{
  563. Frame: oscar.SNACFrame{
  564. FoodGroup: oscar.OService,
  565. SubGroup: oscar.OServiceUserInfoUpdate,
  566. RequestID: 1234,
  567. },
  568. Body: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  569. TLVUserInfo: sess.TLVUserInfo(),
  570. },
  571. }
  572. have := svc.UserInfoQueryHandler(nil, sess, oscar.SNACFrame{RequestID: 1234})
  573. assert.Equal(t, want, have)
  574. }
  575. func TestOServiceService_IdleNotificationHandler(t *testing.T) {
  576. tests := []struct {
  577. name string
  578. sess *state.Session
  579. bodyIn oscar.SNAC_0x01_0x11_OServiceIdleNotification
  580. // recipientScreenName is the screen name of the user receiving the IM
  581. recipientScreenName string
  582. // recipientBuddies is a list of the recipient's buddies that get
  583. // updated warning level
  584. recipientBuddies []string
  585. broadcastMessage oscar.SNACMessage
  586. wantErr error
  587. }{
  588. {
  589. name: "set idle from active",
  590. sess: newTestSession("test-user"),
  591. bodyIn: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
  592. IdleTime: 90,
  593. },
  594. recipientScreenName: "test-user",
  595. recipientBuddies: []string{"buddy1", "buddy2"},
  596. broadcastMessage: oscar.SNACMessage{
  597. Frame: oscar.SNACFrame{
  598. FoodGroup: oscar.Buddy,
  599. SubGroup: oscar.BuddyArrived,
  600. },
  601. Body: oscar.SNAC_0x03_0x0B_BuddyArrived{
  602. TLVUserInfo: newTestSession("test-user", sessOptIdle(90*time.Second)).TLVUserInfo(),
  603. },
  604. },
  605. },
  606. {
  607. name: "set active from idle",
  608. sess: newTestSession("test-user", sessOptIdle(90*time.Second)),
  609. bodyIn: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
  610. IdleTime: 0,
  611. },
  612. recipientScreenName: "test-user",
  613. recipientBuddies: []string{"buddy1", "buddy2"},
  614. broadcastMessage: oscar.SNACMessage{
  615. Frame: oscar.SNACFrame{
  616. FoodGroup: oscar.Buddy,
  617. SubGroup: oscar.BuddyArrived,
  618. },
  619. Body: oscar.SNAC_0x03_0x0B_BuddyArrived{
  620. TLVUserInfo: newTestSession("test-user").TLVUserInfo(),
  621. },
  622. },
  623. },
  624. }
  625. for _, tt := range tests {
  626. t.Run(tt.name, func(t *testing.T) {
  627. feedbagManager := newMockFeedbagManager(t)
  628. feedbagManager.EXPECT().
  629. AdjacentUsers(tt.recipientScreenName).
  630. Return(tt.recipientBuddies, nil).
  631. Maybe()
  632. messageRelayer := newMockMessageRelayer(t)
  633. messageRelayer.EXPECT().
  634. RelayToScreenNames(mock.Anything, tt.recipientBuddies, tt.broadcastMessage).
  635. Maybe()
  636. svc := NewOServiceService(config.Config{}, messageRelayer, feedbagManager)
  637. haveErr := svc.IdleNotificationHandler(nil, tt.sess, tt.bodyIn)
  638. assert.ErrorIs(t, tt.wantErr, haveErr)
  639. })
  640. }
  641. }
  642. func TestOServiceServiceForBOS_ClientOnlineHandler(t *testing.T) {
  643. type buddiesLookupParams []struct {
  644. screenName string
  645. buddies []string
  646. }
  647. tests := []struct {
  648. // name is the name of the test
  649. name string
  650. // joiningChatter is the session of the arriving user
  651. sess *state.Session
  652. // bodyIn is the SNAC body sent from the arriving user's client to the
  653. // server
  654. bodyIn oscar.SNAC_0x01_0x02_OServiceClientOnline
  655. // buddyLookupParams contains params for looking up arriving user's
  656. // buddies
  657. buddyLookupParams buddiesLookupParams
  658. // interestedUsersParams contains params for looking up users who have
  659. // the arriving user on their buddy list
  660. interestedUsersParams interestedUsersParams
  661. // broadcastToScreenNamesParams contains params for sending
  662. // buddy online notification to users who have the arriving user on
  663. // their buddy list
  664. broadcastToScreenNamesParams broadcastToScreenNamesParams
  665. // retrieveByScreenNameParams contains params for looking up the
  666. // session for each of the arriving user's buddies
  667. retrieveByScreenNameParams retrieveByScreenNameParams
  668. // sendToScreenNameParams contains params for sending arrival
  669. // notifications for each of the arriving user's buddies to the
  670. // arriving user's client
  671. sendToScreenNameParams sendToScreenNameParams
  672. wantErr error
  673. }{
  674. {
  675. name: "notify arriving user's buddies of its arrival and populate the arriving user's buddy list",
  676. sess: newTestSession("test-user"),
  677. bodyIn: oscar.SNAC_0x01_0x02_OServiceClientOnline{},
  678. interestedUsersParams: interestedUsersParams{
  679. {
  680. screenName: "test-user",
  681. users: []string{"buddy1", "buddy2", "buddy3", "buddy4"},
  682. },
  683. },
  684. broadcastToScreenNamesParams: broadcastToScreenNamesParams{
  685. {
  686. screenNames: []string{"buddy1", "buddy2", "buddy3", "buddy4"},
  687. message: oscar.SNACMessage{
  688. Frame: oscar.SNACFrame{
  689. FoodGroup: oscar.Buddy,
  690. SubGroup: oscar.BuddyArrived,
  691. },
  692. Body: oscar.SNAC_0x03_0x0B_BuddyArrived{
  693. TLVUserInfo: newTestSession("test-user").TLVUserInfo(),
  694. },
  695. },
  696. },
  697. },
  698. buddyLookupParams: buddiesLookupParams{
  699. {
  700. screenName: "test-user",
  701. buddies: []string{"buddy1", "buddy3"},
  702. },
  703. },
  704. retrieveByScreenNameParams: retrieveByScreenNameParams{
  705. {
  706. screenName: "buddy1",
  707. sess: newTestSession("buddy1"),
  708. },
  709. {
  710. screenName: "buddy3",
  711. sess: newTestSession("buddy3"),
  712. },
  713. },
  714. sendToScreenNameParams: sendToScreenNameParams{
  715. {
  716. screenName: "test-user",
  717. message: oscar.SNACMessage{
  718. Frame: oscar.SNACFrame{
  719. FoodGroup: oscar.Buddy,
  720. SubGroup: oscar.BuddyArrived,
  721. },
  722. Body: oscar.SNAC_0x03_0x0B_BuddyArrived{
  723. TLVUserInfo: newTestSession("buddy1").TLVUserInfo(),
  724. },
  725. },
  726. },
  727. {
  728. screenName: "test-user",
  729. message: oscar.SNACMessage{
  730. Frame: oscar.SNACFrame{
  731. FoodGroup: oscar.Buddy,
  732. SubGroup: oscar.BuddyArrived,
  733. },
  734. Body: oscar.SNAC_0x03_0x0B_BuddyArrived{
  735. TLVUserInfo: newTestSession("buddy3").TLVUserInfo(),
  736. },
  737. },
  738. },
  739. },
  740. },
  741. }
  742. for _, tt := range tests {
  743. t.Run(tt.name, func(t *testing.T) {
  744. feedbagManager := newMockFeedbagManager(t)
  745. messageRelayer := newMockMessageRelayer(t)
  746. for _, params := range tt.interestedUsersParams {
  747. feedbagManager.EXPECT().
  748. AdjacentUsers(params.screenName).
  749. Return(params.users, nil)
  750. }
  751. for _, params := range tt.broadcastToScreenNamesParams {
  752. messageRelayer.EXPECT().
  753. RelayToScreenNames(mock.Anything, params.screenNames, params.message)
  754. }
  755. for _, params := range tt.buddyLookupParams {
  756. feedbagManager.EXPECT().
  757. Buddies(params.screenName).
  758. Return(params.buddies, nil)
  759. }
  760. for _, params := range tt.retrieveByScreenNameParams {
  761. messageRelayer.EXPECT().
  762. RetrieveByScreenName(params.screenName).
  763. Return(params.sess)
  764. }
  765. for _, params := range tt.sendToScreenNameParams {
  766. messageRelayer.EXPECT().
  767. RelayToScreenName(mock.Anything, params.screenName, params.message)
  768. }
  769. svc := NewOServiceServiceForBOS(OServiceService{
  770. feedbagManager: feedbagManager,
  771. messageRelayer: messageRelayer,
  772. }, nil)
  773. haveErr := svc.ClientOnlineHandler(nil, tt.bodyIn, tt.sess)
  774. assert.ErrorIs(t, tt.wantErr, haveErr)
  775. })
  776. }
  777. }
  778. func TestOServiceServiceForChat_ClientOnlineHandler(t *testing.T) {
  779. chatter1 := newTestSession("chatter-1")
  780. chatter2 := newTestSession("chatter-2")
  781. chatRoom := state.ChatRoom{
  782. Cookie: "the-cookie",
  783. DetailLevel: 1,
  784. Exchange: 2,
  785. InstanceNumber: 3,
  786. Name: "the-chat-room",
  787. }
  788. type participantsParams []*state.Session
  789. type broadcastExcept []struct {
  790. sess *state.Session
  791. message oscar.SNACMessage
  792. }
  793. type sendToScreenNameParams []struct {
  794. screenName string
  795. message oscar.SNACMessage
  796. }
  797. tests := []struct {
  798. // name is the name of the test
  799. name string
  800. // joiningChatter is the user joining the chat room
  801. joiningChatter *state.Session
  802. // bodyIn is the SNAC body sent from the arriving user's client to the
  803. // server
  804. bodyIn oscar.SNAC_0x01_0x02_OServiceClientOnline
  805. // participantsParams contains all the chat room participants
  806. participantsParams participantsParams
  807. // broadcastExcept contains params for broadcasting chat arrival to all
  808. // chat participants except the user joining
  809. broadcastExcept broadcastExcept
  810. // sendToScreenNameParams contains params for sending chat room
  811. // metadata and chat participant list to joining user
  812. sendToScreenNameParams sendToScreenNameParams
  813. wantErr error
  814. }{
  815. {
  816. name: "upon joining, send chat room metadata and participant list to joining user; alert arrival to existing participants",
  817. joiningChatter: chatter1,
  818. bodyIn: oscar.SNAC_0x01_0x02_OServiceClientOnline{},
  819. broadcastExcept: broadcastExcept{
  820. {
  821. sess: chatter1,
  822. message: oscar.SNACMessage{
  823. Frame: oscar.SNACFrame{
  824. FoodGroup: oscar.Chat,
  825. SubGroup: oscar.ChatUsersJoined,
  826. },
  827. Body: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
  828. Users: []oscar.TLVUserInfo{
  829. chatter1.TLVUserInfo(),
  830. },
  831. },
  832. },
  833. },
  834. },
  835. participantsParams: participantsParams{
  836. chatter1,
  837. chatter2,
  838. },
  839. sendToScreenNameParams: sendToScreenNameParams{
  840. {
  841. screenName: chatter1.ScreenName(),
  842. message: oscar.SNACMessage{
  843. Frame: oscar.SNACFrame{
  844. FoodGroup: oscar.Chat,
  845. SubGroup: oscar.ChatRoomInfoUpdate,
  846. },
  847. Body: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
  848. Exchange: chatRoom.Exchange,
  849. Cookie: chatRoom.Cookie,
  850. InstanceNumber: chatRoom.InstanceNumber,
  851. DetailLevel: chatRoom.DetailLevel,
  852. TLVBlock: oscar.TLVBlock{
  853. TLVList: chatRoom.TLVList(),
  854. },
  855. },
  856. },
  857. },
  858. {
  859. screenName: chatter1.ScreenName(),
  860. message: oscar.SNACMessage{
  861. Frame: oscar.SNACFrame{
  862. FoodGroup: oscar.Chat,
  863. SubGroup: oscar.ChatUsersJoined,
  864. },
  865. Body: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
  866. Users: []oscar.TLVUserInfo{
  867. chatter1.TLVUserInfo(),
  868. chatter2.TLVUserInfo(),
  869. },
  870. },
  871. },
  872. },
  873. },
  874. },
  875. }
  876. for _, tt := range tests {
  877. t.Run(tt.name, func(t *testing.T) {
  878. feedbagManager := newMockFeedbagManager(t)
  879. chatMessageRelayer := newMockChatMessageRelayer(t)
  880. for _, params := range tt.broadcastExcept {
  881. chatMessageRelayer.EXPECT().
  882. RelayToAllExcept(mock.Anything, params.sess, params.message).
  883. Maybe()
  884. }
  885. chatMessageRelayer.EXPECT().
  886. AllSessions().
  887. Return(tt.participantsParams).
  888. Maybe()
  889. for _, params := range tt.sendToScreenNameParams {
  890. chatMessageRelayer.EXPECT().
  891. RelayToScreenName(mock.Anything, params.screenName, params.message).
  892. Maybe()
  893. }
  894. chatRegistry := state.NewChatRegistry()
  895. chatRegistry.Register(chatRoom, chatMessageRelayer)
  896. svc := NewOServiceServiceForChat(OServiceService{
  897. feedbagManager: feedbagManager,
  898. messageRelayer: chatMessageRelayer,
  899. }, chatRegistry)
  900. haveErr := svc.ClientOnlineHandler(nil, tt.joiningChatter, chatRoom.Cookie)
  901. assert.ErrorIs(t, tt.wantErr, haveErr)
  902. })
  903. }
  904. }