oservice_test.go 26 KB

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