feedbag_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. package server
  2. import (
  3. "bytes"
  4. "github.com/mkaminski/goaim/oscar"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/mock"
  7. "testing"
  8. "time"
  9. )
  10. func TestQueryHandler(t *testing.T) {
  11. cases := []struct {
  12. // name is the unit test name
  13. name string
  14. // screenName is the buddy list owner
  15. screenName string
  16. // feedbagItems is the list of items in user's buddy list
  17. feedbagItems []oscar.FeedbagItem
  18. // lastModified is the time the buddy list was last changed
  19. lastModified time.Time
  20. // expectOutput is the SNAC payload sent from the server to the
  21. // recipient client
  22. expectOutput XMessage
  23. }{
  24. {
  25. name: "retrieve empty feedbag",
  26. screenName: "user_screen_name",
  27. feedbagItems: []oscar.FeedbagItem{},
  28. lastModified: time.UnixMilli(0),
  29. expectOutput: XMessage{
  30. snacFrame: oscar.SnacFrame{
  31. FoodGroup: oscar.FEEDBAG,
  32. SubGroup: oscar.FeedbagReply,
  33. },
  34. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  35. Items: []oscar.FeedbagItem{},
  36. },
  37. },
  38. },
  39. {
  40. name: "retrieve feedbag with items",
  41. screenName: "user_screen_name",
  42. feedbagItems: []oscar.FeedbagItem{
  43. {
  44. Name: "buddy_1",
  45. },
  46. {
  47. Name: "buddy_2",
  48. },
  49. },
  50. lastModified: time.UnixMilli(1696472198082),
  51. expectOutput: XMessage{
  52. snacFrame: oscar.SnacFrame{
  53. FoodGroup: oscar.FEEDBAG,
  54. SubGroup: oscar.FeedbagReply,
  55. },
  56. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  57. Version: 0,
  58. Items: []oscar.FeedbagItem{
  59. {
  60. Name: "buddy_1",
  61. },
  62. {
  63. Name: "buddy_2",
  64. },
  65. },
  66. LastUpdate: uint32(time.UnixMilli(1696472198082).Unix()),
  67. },
  68. },
  69. },
  70. }
  71. for _, tc := range cases {
  72. t.Run(tc.name, func(t *testing.T) {
  73. //
  74. // initialize dependencies
  75. //
  76. fm := NewMockFeedbagManager(t)
  77. fm.EXPECT().
  78. Retrieve(tc.screenName).
  79. Return(tc.feedbagItems, nil).
  80. Maybe()
  81. fm.EXPECT().
  82. LastModified(tc.screenName).
  83. Return(tc.lastModified, nil).
  84. Maybe()
  85. //
  86. // send input SNAC
  87. //
  88. senderSession := &Session{
  89. ScreenName: tc.screenName,
  90. }
  91. svc := FeedbagService{}
  92. outputSNAC, err := svc.QueryHandler(senderSession, fm)
  93. assert.NoError(t, err)
  94. assert.Equal(t, tc.expectOutput, outputSNAC)
  95. })
  96. }
  97. }
  98. func TestQueryIfModifiedHandler(t *testing.T) {
  99. cases := []struct {
  100. // name is the unit test name
  101. name string
  102. // screenName is the buddy list owner
  103. screenName string
  104. // feedbagItems is the list of items in user's buddy list
  105. feedbagItems []oscar.FeedbagItem
  106. // lastModified is the time the buddy list was last changed
  107. lastModified time.Time
  108. // inputSNAC is the SNAC sent by the sender client
  109. inputSNAC oscar.SNAC_0x13_0x05_FeedbagQueryIfModified
  110. // expectOutput is the SNAC payload sent from the server to the
  111. // recipient client
  112. expectOutput XMessage
  113. }{
  114. {
  115. name: "retrieve empty feedbag",
  116. screenName: "user_screen_name",
  117. feedbagItems: []oscar.FeedbagItem{},
  118. lastModified: time.UnixMilli(0),
  119. inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  120. LastUpdate: uint32(time.UnixMilli(100000).Unix()),
  121. },
  122. expectOutput: XMessage{
  123. snacFrame: oscar.SnacFrame{
  124. FoodGroup: oscar.FEEDBAG,
  125. SubGroup: oscar.FeedbagReply,
  126. },
  127. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  128. Items: []oscar.FeedbagItem{},
  129. },
  130. },
  131. },
  132. {
  133. name: "retrieve feedbag with items",
  134. screenName: "user_screen_name",
  135. feedbagItems: []oscar.FeedbagItem{
  136. {
  137. Name: "buddy_1",
  138. },
  139. {
  140. Name: "buddy_2",
  141. },
  142. },
  143. lastModified: time.UnixMilli(200000),
  144. inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  145. LastUpdate: uint32(time.UnixMilli(100000).Unix()),
  146. },
  147. expectOutput: XMessage{
  148. snacFrame: oscar.SnacFrame{
  149. FoodGroup: oscar.FEEDBAG,
  150. SubGroup: oscar.FeedbagReply,
  151. },
  152. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  153. Version: 0,
  154. Items: []oscar.FeedbagItem{
  155. {
  156. Name: "buddy_1",
  157. },
  158. {
  159. Name: "buddy_2",
  160. },
  161. },
  162. LastUpdate: uint32(time.UnixMilli(200000).Unix()),
  163. },
  164. },
  165. },
  166. {
  167. name: "retrieve not-modified response",
  168. screenName: "user_screen_name",
  169. feedbagItems: []oscar.FeedbagItem{
  170. {
  171. Name: "buddy_1",
  172. },
  173. {
  174. Name: "buddy_2",
  175. },
  176. },
  177. lastModified: time.UnixMilli(100000),
  178. inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  179. LastUpdate: uint32(time.UnixMilli(200000).Unix()),
  180. },
  181. expectOutput: XMessage{
  182. snacFrame: oscar.SnacFrame{
  183. FoodGroup: oscar.FEEDBAG,
  184. SubGroup: oscar.FeedbagReplyNotModified,
  185. },
  186. snacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  187. LastUpdate: uint32(time.UnixMilli(100000).Unix()),
  188. Count: 2,
  189. },
  190. },
  191. },
  192. }
  193. for _, tc := range cases {
  194. t.Run(tc.name, func(t *testing.T) {
  195. //
  196. // initialize dependencies
  197. //
  198. fm := NewMockFeedbagManager(t)
  199. fm.EXPECT().
  200. Retrieve(tc.screenName).
  201. Return(tc.feedbagItems, nil).
  202. Maybe()
  203. fm.EXPECT().
  204. LastModified(tc.screenName).
  205. Return(tc.lastModified, nil).
  206. Maybe()
  207. //
  208. // send input SNAC
  209. //
  210. senderSession := &Session{
  211. ScreenName: tc.screenName,
  212. }
  213. svc := FeedbagService{}
  214. outputSNAC, err := svc.QueryIfModifiedHandler(senderSession, fm, tc.inputSNAC)
  215. assert.NoError(t, err)
  216. //
  217. // verify output
  218. //
  219. assert.Equal(t, tc.expectOutput, outputSNAC)
  220. })
  221. }
  222. }
  223. func TestInsertItemHandler(t *testing.T) {
  224. defaultSess := &Session{}
  225. cases := []struct {
  226. // name is the unit test name
  227. name string
  228. // userSession is the session of the user managing buddy list
  229. userSession *Session
  230. // feedbagItems is the list of items in user's buddy list
  231. feedbagItems []oscar.FeedbagItem
  232. // inputSNAC is the SNAC sent by the sender client
  233. inputSNAC oscar.SNAC_0x13_0x08_FeedbagInsertItem
  234. // screenNameLookups is the list of user's online buddies
  235. screenNameLookups map[string]struct {
  236. sess *Session
  237. err error
  238. }
  239. // clientResponse is the message returned to the client
  240. clientResponse XMessage
  241. // buddyMessages are events forwarded to buddy clients
  242. buddyMessages []struct {
  243. user string
  244. msg XMessage
  245. }
  246. }{
  247. {
  248. name: "user adds 2 online buddies, expect OK response",
  249. userSession: &Session{
  250. ScreenName: "user_screen_name",
  251. },
  252. inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  253. Items: []oscar.FeedbagItem{
  254. {
  255. ClassID: 2,
  256. Name: "buddy_1_online",
  257. },
  258. {
  259. ClassID: 2,
  260. Name: "buddy_2_online",
  261. },
  262. },
  263. },
  264. screenNameLookups: map[string]struct {
  265. sess *Session
  266. err error
  267. }{
  268. "user_screen_name": {
  269. sess: &Session{ScreenName: "user_screen_name"},
  270. },
  271. "buddy_1_online": {
  272. sess: &Session{ScreenName: "buddy_1_online"},
  273. },
  274. "buddy_2_online": {
  275. sess: &Session{ScreenName: "buddy_2_online"},
  276. },
  277. },
  278. clientResponse: XMessage{
  279. snacFrame: oscar.SnacFrame{
  280. FoodGroup: oscar.FEEDBAG,
  281. SubGroup: oscar.FeedbagStatus,
  282. },
  283. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  284. Results: []uint16{0x0000, 0x0000},
  285. },
  286. },
  287. buddyMessages: []struct {
  288. user string
  289. msg XMessage
  290. }{
  291. {
  292. user: "user_screen_name",
  293. msg: XMessage{
  294. snacFrame: oscar.SnacFrame{
  295. FoodGroup: oscar.BUDDY,
  296. SubGroup: oscar.BuddyArrived,
  297. },
  298. snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
  299. TLVUserInfo: oscar.TLVUserInfo{
  300. ScreenName: "buddy_1_online",
  301. TLVBlock: oscar.TLVBlock{
  302. TLVList: defaultSess.GetUserInfo(),
  303. },
  304. },
  305. },
  306. },
  307. },
  308. {
  309. user: "user_screen_name",
  310. msg: XMessage{
  311. snacFrame: oscar.SnacFrame{
  312. FoodGroup: oscar.BUDDY,
  313. SubGroup: oscar.BuddyArrived,
  314. },
  315. snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
  316. TLVUserInfo: oscar.TLVUserInfo{
  317. ScreenName: "buddy_2_online",
  318. TLVBlock: oscar.TLVBlock{
  319. TLVList: defaultSess.GetUserInfo(),
  320. },
  321. },
  322. },
  323. },
  324. },
  325. },
  326. },
  327. {
  328. name: "user adds an offline buddy, expect OK response and 0 buddy arrived events",
  329. userSession: &Session{
  330. ScreenName: "user_screen_name",
  331. },
  332. inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  333. Items: []oscar.FeedbagItem{
  334. {
  335. ClassID: 2,
  336. Name: "buddy_offline",
  337. },
  338. },
  339. },
  340. screenNameLookups: map[string]struct {
  341. sess *Session
  342. err error
  343. }{
  344. "buddy_offline": {
  345. err: ErrSessNotFound,
  346. },
  347. },
  348. clientResponse: XMessage{
  349. snacFrame: oscar.SnacFrame{
  350. FoodGroup: oscar.FEEDBAG,
  351. SubGroup: oscar.FeedbagStatus,
  352. },
  353. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  354. Results: []uint16{0x0000},
  355. },
  356. },
  357. },
  358. {
  359. name: "users adds an invisible buddy, expect OK response and 0 buddy arrived events",
  360. userSession: &Session{
  361. ScreenName: "user_screen_name",
  362. },
  363. inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  364. Items: []oscar.FeedbagItem{
  365. {
  366. ClassID: 2,
  367. Name: "invisible_buddy_online",
  368. },
  369. },
  370. },
  371. screenNameLookups: map[string]struct {
  372. sess *Session
  373. err error
  374. }{
  375. "invisible_buddy_online": {
  376. sess: &Session{
  377. ScreenName: "invisible_buddy_online",
  378. invisible: true,
  379. },
  380. },
  381. },
  382. clientResponse: XMessage{
  383. snacFrame: oscar.SnacFrame{
  384. FoodGroup: oscar.FEEDBAG,
  385. SubGroup: oscar.FeedbagStatus,
  386. },
  387. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  388. Results: []uint16{0x0000},
  389. },
  390. },
  391. },
  392. {
  393. name: "user blocks buddy currently online, expect OK response, buddy departed event client, 1 buddy " +
  394. "departed event sent to buddy",
  395. userSession: &Session{
  396. ScreenName: "user_screen_name",
  397. },
  398. inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  399. Items: []oscar.FeedbagItem{
  400. {
  401. ClassID: 3,
  402. Name: "buddy_1",
  403. },
  404. },
  405. },
  406. screenNameLookups: map[string]struct {
  407. sess *Session
  408. err error
  409. }{
  410. "user_screen_name": {
  411. sess: &Session{ScreenName: "user_screen_name"},
  412. },
  413. "buddy_1": {
  414. sess: &Session{ScreenName: "buddy_1"},
  415. },
  416. },
  417. buddyMessages: []struct {
  418. user string
  419. msg XMessage
  420. }{
  421. {
  422. user: "buddy_1",
  423. msg: XMessage{
  424. snacFrame: oscar.SnacFrame{
  425. FoodGroup: oscar.BUDDY,
  426. SubGroup: oscar.BuddyDeparted,
  427. },
  428. snacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
  429. TLVUserInfo: oscar.TLVUserInfo{
  430. ScreenName: "user_screen_name",
  431. WarningLevel: 0,
  432. },
  433. },
  434. },
  435. },
  436. {
  437. user: "user_screen_name",
  438. msg: XMessage{
  439. snacFrame: oscar.SnacFrame{
  440. FoodGroup: oscar.BUDDY,
  441. SubGroup: oscar.BuddyDeparted,
  442. },
  443. snacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
  444. TLVUserInfo: oscar.TLVUserInfo{
  445. ScreenName: "buddy_1",
  446. WarningLevel: 0,
  447. },
  448. },
  449. },
  450. },
  451. },
  452. clientResponse: XMessage{
  453. snacFrame: oscar.SnacFrame{
  454. FoodGroup: oscar.FEEDBAG,
  455. SubGroup: oscar.FeedbagStatus,
  456. },
  457. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  458. Results: []uint16{0x0000},
  459. },
  460. },
  461. },
  462. {
  463. name: "user blocks buddy currently offline, expect OK response and no buddy departed events",
  464. userSession: &Session{
  465. ScreenName: "user_screen_name",
  466. },
  467. inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  468. Items: []oscar.FeedbagItem{
  469. {
  470. ClassID: 3,
  471. Name: "buddy_1",
  472. },
  473. },
  474. },
  475. screenNameLookups: map[string]struct {
  476. sess *Session
  477. err error
  478. }{
  479. "user_screen_name": {
  480. sess: &Session{ScreenName: "user_screen_name"},
  481. },
  482. "buddy_1": {
  483. err: ErrSessNotFound,
  484. },
  485. },
  486. clientResponse: XMessage{
  487. snacFrame: oscar.SnacFrame{
  488. FoodGroup: oscar.FEEDBAG,
  489. SubGroup: oscar.FeedbagStatus,
  490. },
  491. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  492. Results: []uint16{0x0000},
  493. },
  494. },
  495. },
  496. {
  497. name: "user tries to block themselves, expect feedback error",
  498. userSession: &Session{
  499. ScreenName: "user_screen_name",
  500. },
  501. inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  502. Items: []oscar.FeedbagItem{
  503. {
  504. ClassID: 3,
  505. Name: "user_screen_name",
  506. },
  507. },
  508. },
  509. clientResponse: XMessage{
  510. snacFrame: oscar.SnacFrame{
  511. FoodGroup: oscar.FEEDBAG,
  512. SubGroup: oscar.FeedbagErr,
  513. },
  514. snacOut: oscar.SnacError{
  515. Code: ErrorCodeNotSupportedByHost,
  516. },
  517. },
  518. },
  519. }
  520. for _, tc := range cases {
  521. t.Run(tc.name, func(t *testing.T) {
  522. //
  523. // initialize dependencies
  524. //
  525. fm := NewMockFeedbagManager(t)
  526. fm.EXPECT().
  527. Upsert(tc.userSession.ScreenName, tc.inputSNAC.Items).
  528. Return(nil).
  529. Maybe()
  530. fm.EXPECT().
  531. Buddies(tc.userSession.ScreenName).
  532. Return([]string{}, nil).
  533. Maybe()
  534. sm := NewMockSessionManager(t)
  535. for screenName, val := range tc.screenNameLookups {
  536. sm.EXPECT().
  537. RetrieveByScreenName(screenName).
  538. Return(val.sess, val.err).
  539. Maybe()
  540. }
  541. for _, n := range tc.buddyMessages {
  542. sm.EXPECT().
  543. SendToScreenName(n.user, n.msg).
  544. Maybe()
  545. }
  546. //
  547. // send input SNAC
  548. //
  549. svc := FeedbagService{}
  550. output, err := svc.InsertItemHandler(sm, tc.userSession, fm, tc.inputSNAC)
  551. assert.NoError(t, err)
  552. //
  553. // verify response
  554. //
  555. assert.Equal(t, output, tc.clientResponse)
  556. })
  557. }
  558. }
  559. func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
  560. cases := []struct {
  561. // name is the unit test name
  562. name string
  563. // input is the request payload
  564. input XMessage
  565. // output is the response payload
  566. output XMessage
  567. // handlerErr is the mocked handler error response
  568. handlerErr error
  569. // expectErr is the expected error returned by the router
  570. expectErr error
  571. }{
  572. {
  573. name: "receive FeedbagRightsQuery, return FeedbagRightsReply",
  574. input: XMessage{
  575. snacFrame: oscar.SnacFrame{
  576. FoodGroup: oscar.FEEDBAG,
  577. SubGroup: oscar.FeedbagRightsQuery,
  578. },
  579. snacOut: oscar.SNAC_0x13_0x02_FeedbagRightsQuery{
  580. TLVRestBlock: oscar.TLVRestBlock{
  581. TLVList: oscar.TLVList{
  582. {
  583. TType: 0x01,
  584. Val: []byte{1, 2, 3, 4},
  585. },
  586. },
  587. },
  588. },
  589. },
  590. output: XMessage{
  591. snacFrame: oscar.SnacFrame{
  592. FoodGroup: oscar.FEEDBAG,
  593. SubGroup: oscar.FeedbagRightsReply,
  594. },
  595. snacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
  596. TLVRestBlock: oscar.TLVRestBlock{
  597. TLVList: oscar.TLVList{
  598. {
  599. TType: 0x01,
  600. Val: []byte{1, 2, 3, 4},
  601. },
  602. },
  603. },
  604. },
  605. },
  606. },
  607. {
  608. name: "receive FeedbagQuery, return FeedbagReply",
  609. input: XMessage{
  610. snacFrame: oscar.SnacFrame{
  611. FoodGroup: oscar.FEEDBAG,
  612. SubGroup: oscar.FeedbagQuery,
  613. },
  614. snacOut: oscar.SNAC_0x13_0x02_FeedbagRightsQuery{
  615. TLVRestBlock: oscar.TLVRestBlock{
  616. TLVList: oscar.TLVList{
  617. {
  618. TType: 0x01,
  619. Val: []byte{1, 2, 3, 4},
  620. },
  621. },
  622. },
  623. },
  624. },
  625. output: XMessage{
  626. snacFrame: oscar.SnacFrame{
  627. FoodGroup: oscar.FEEDBAG,
  628. SubGroup: oscar.FeedbagReply,
  629. },
  630. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  631. Version: 4,
  632. },
  633. },
  634. },
  635. {
  636. name: "receive FeedbagQueryIfModified, return FeedbagRightsReply",
  637. input: XMessage{
  638. snacFrame: oscar.SnacFrame{
  639. FoodGroup: oscar.FEEDBAG,
  640. SubGroup: oscar.FeedbagQueryIfModified,
  641. },
  642. snacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  643. LastUpdate: 1234,
  644. },
  645. },
  646. output: XMessage{
  647. snacFrame: oscar.SnacFrame{
  648. FoodGroup: oscar.FEEDBAG,
  649. SubGroup: oscar.FeedbagReply,
  650. },
  651. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  652. LastUpdate: 1234,
  653. },
  654. },
  655. },
  656. {
  657. name: "receive FeedbagUse, return no response",
  658. input: XMessage{
  659. snacFrame: oscar.SnacFrame{
  660. FoodGroup: oscar.FEEDBAG,
  661. SubGroup: oscar.FeedbagUse,
  662. },
  663. snacOut: struct{}{},
  664. },
  665. output: XMessage{},
  666. },
  667. {
  668. name: "receive FeedbagInsertItem, return BuddyArrived and FeedbagStatus",
  669. input: XMessage{
  670. snacFrame: oscar.SnacFrame{
  671. FoodGroup: oscar.FEEDBAG,
  672. SubGroup: oscar.FeedbagInsertItem,
  673. },
  674. snacOut: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
  675. Items: []oscar.FeedbagItem{
  676. {
  677. Name: "my-item",
  678. },
  679. },
  680. },
  681. },
  682. output: XMessage{
  683. snacFrame: oscar.SnacFrame{
  684. FoodGroup: oscar.FEEDBAG,
  685. SubGroup: oscar.FeedbagStatus,
  686. },
  687. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  688. Results: []uint16{1234},
  689. },
  690. },
  691. },
  692. {
  693. name: "receive FeedbagUpdateItem, return BuddyArrived and FeedbagStatus",
  694. input: XMessage{
  695. snacFrame: oscar.SnacFrame{
  696. FoodGroup: oscar.FEEDBAG,
  697. SubGroup: oscar.FeedbagUpdateItem,
  698. },
  699. snacOut: oscar.SNAC_0x13_0x09_FeedbagUpdateItem{
  700. Items: []oscar.FeedbagItem{
  701. {
  702. Name: "my-item",
  703. },
  704. },
  705. },
  706. },
  707. output: XMessage{
  708. snacFrame: oscar.SnacFrame{
  709. FoodGroup: oscar.FEEDBAG,
  710. SubGroup: oscar.FeedbagStatus,
  711. },
  712. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  713. Results: []uint16{1234},
  714. },
  715. },
  716. },
  717. {
  718. name: "receive FeedbagDeleteItem, return FeedbagStatus",
  719. input: XMessage{
  720. snacFrame: oscar.SnacFrame{
  721. FoodGroup: oscar.FEEDBAG,
  722. SubGroup: oscar.FeedbagDeleteItem,
  723. },
  724. snacOut: oscar.SNAC_0x13_0x0A_FeedbagDeleteItem{
  725. Items: []oscar.FeedbagItem{
  726. {
  727. Name: "my-item",
  728. },
  729. },
  730. },
  731. },
  732. output: XMessage{
  733. snacFrame: oscar.SnacFrame{
  734. FoodGroup: oscar.FEEDBAG,
  735. SubGroup: oscar.FeedbagStatus,
  736. },
  737. snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
  738. Results: []uint16{1234},
  739. },
  740. },
  741. },
  742. {
  743. name: "receive FeedbagStartCluster, return no response",
  744. input: XMessage{
  745. snacFrame: oscar.SnacFrame{
  746. FoodGroup: oscar.FEEDBAG,
  747. SubGroup: oscar.FeedbagStartCluster,
  748. },
  749. snacOut: oscar.SNAC_0x13_0x11_FeedbagStartCluster{
  750. TLVRestBlock: oscar.TLVRestBlock{
  751. TLVList: oscar.TLVList{
  752. {
  753. TType: 0x01,
  754. Val: []byte{1, 2, 3, 4},
  755. },
  756. },
  757. },
  758. },
  759. },
  760. output: XMessage{},
  761. },
  762. {
  763. name: "receive FeedbagEndCluster, return no response",
  764. input: XMessage{
  765. snacFrame: oscar.SnacFrame{
  766. FoodGroup: oscar.FEEDBAG,
  767. SubGroup: oscar.FeedbagEndCluster,
  768. },
  769. snacOut: struct{}{},
  770. },
  771. output: XMessage{},
  772. },
  773. {
  774. name: "receive FeedbagDeleteUser, return ErrUnsupportedSubGroup",
  775. input: XMessage{
  776. snacFrame: oscar.SnacFrame{
  777. FoodGroup: oscar.FEEDBAG,
  778. SubGroup: oscar.FeedbagDeleteUser,
  779. },
  780. snacOut: struct{}{},
  781. },
  782. output: XMessage{},
  783. expectErr: ErrUnsupportedSubGroup,
  784. },
  785. }
  786. for _, tc := range cases {
  787. t.Run(tc.name, func(t *testing.T) {
  788. svc := NewMockFeedbagHandler(t)
  789. svc.EXPECT().
  790. DeleteItemHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  791. Return(tc.output, tc.handlerErr).
  792. Maybe()
  793. svc.EXPECT().
  794. QueryHandler(mock.Anything, mock.Anything).
  795. Return(tc.output, tc.handlerErr).
  796. Maybe()
  797. svc.EXPECT().
  798. QueryIfModifiedHandler(mock.Anything, mock.Anything, tc.input.snacOut).
  799. Return(tc.output, tc.handlerErr).
  800. Maybe()
  801. svc.EXPECT().
  802. RightsQueryHandler().
  803. Return(tc.output).
  804. Maybe()
  805. svc.EXPECT().
  806. InsertItemHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  807. Return(tc.output, tc.handlerErr).
  808. Maybe()
  809. svc.EXPECT().
  810. UpdateItemHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  811. Return(tc.output, tc.handlerErr).
  812. Maybe()
  813. svc.EXPECT().
  814. StartClusterHandler(tc.input.snacOut).
  815. Maybe()
  816. router := FeedbagRouter{
  817. FeedbagHandler: svc,
  818. }
  819. bufIn := &bytes.Buffer{}
  820. assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
  821. bufOut := &bytes.Buffer{}
  822. seq := uint32(0)
  823. err := router.RouteFeedbag(nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
  824. assert.ErrorIs(t, err, tc.expectErr)
  825. if tc.expectErr != nil {
  826. return
  827. }
  828. if tc.output.snacFrame == (oscar.SnacFrame{}) {
  829. return
  830. }
  831. // verify the FLAP frame
  832. flap := oscar.FlapFrame{}
  833. assert.NoError(t, oscar.Unmarshal(&flap, bufOut))
  834. // make sure the sequence increments
  835. assert.Equal(t, seq, uint32(1))
  836. assert.Equal(t, flap.Sequence, uint16(0))
  837. flapBuf, err := flap.SNACBuffer(bufOut)
  838. assert.NoError(t, err)
  839. // verify the SNAC frame
  840. snacFrame := oscar.SnacFrame{}
  841. assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
  842. assert.Equal(t, tc.output.snacFrame, snacFrame)
  843. // verify the SNAC message
  844. snacBuf := &bytes.Buffer{}
  845. assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
  846. assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
  847. })
  848. }
  849. }