bart_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. package foodgroup
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "log/slog"
  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 TestBARTService_UpsertItem(t *testing.T) {
  14. itemHash := []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed}
  15. itemData := []byte{'i', 't', 'e', 'm', 'd', 'a', 't', 'a'}
  16. cases := []struct {
  17. // name is the unit test name
  18. name string
  19. // instance is the session of the user adding to feedbag
  20. instance *state.SessionInstance
  21. // inputSNAC is the SNAC sent from the client to the server
  22. inputSNAC wire.SNACMessage
  23. // mockParams is the list of params sent to mocks that satisfy this
  24. // method's dependencies
  25. mockParams mockParams
  26. // expectOutput is the SNAC sent from the server to client
  27. expectOutput wire.SNACMessage
  28. // wantErr is the expected error
  29. wantErr error
  30. // instanceMatch verifies the session state after completion
  31. instanceMatch func(instance *state.SessionInstance)
  32. }{
  33. {
  34. name: "insert new buddy icon for current session",
  35. instance: newTestInstance("user_screen_name", sessOptBuddyIcon(wire.BARTID{
  36. Type: wire.BARTTypesBuddyIcon,
  37. BARTInfo: wire.BARTInfo{
  38. Flags: wire.BARTFlagsCustom | wire.BARTFlagsUnknown,
  39. Hash: itemHash,
  40. },
  41. })),
  42. inputSNAC: wire.SNACMessage{
  43. Frame: wire.SNACFrame{
  44. RequestID: 1234,
  45. },
  46. Body: wire.SNAC_0x10_0x02_BARTUploadQuery{
  47. Type: wire.BARTTypesBuddyIcon,
  48. Data: itemData,
  49. },
  50. },
  51. mockParams: mockParams{
  52. bartItemManagerParams: bartItemManagerParams{
  53. bartItemManagerUpsertParams: bartItemManagerUpsertParams{
  54. {
  55. itemHash: itemHash,
  56. payload: itemData,
  57. bartType: wire.BARTTypesBuddyIcon,
  58. },
  59. },
  60. },
  61. buddyBroadcasterParams: buddyBroadcasterParams{
  62. broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
  63. {
  64. screenName: state.DisplayScreenName("user_screen_name"),
  65. bodyMatcher: func(tlvInfo wire.TLVUserInfo) bool {
  66. bartID, exists := tlvInfo.Bytes(wire.OServiceUserInfoBARTInfo)
  67. return exists &&
  68. tlvInfo.ScreenName == "user_screen_name" &&
  69. bytes.Contains(bartID, itemHash)
  70. },
  71. },
  72. },
  73. },
  74. messageRelayerParams: messageRelayerParams{
  75. relayToScreenNameParams: relayToScreenNameParams{
  76. {
  77. screenName: state.NewIdentScreenName("user_screen_name"),
  78. message: wire.SNACMessage{
  79. Frame: wire.SNACFrame{
  80. FoodGroup: wire.OService,
  81. SubGroup: wire.OServiceUserInfoUpdate,
  82. },
  83. Body: func(val any) bool {
  84. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  85. if !ok {
  86. return false
  87. }
  88. bartID, exists := snac.UserInfo[0].Bytes(wire.OServiceUserInfoBARTInfo)
  89. return exists &&
  90. snac.UserInfo[0].ScreenName == "user_screen_name" &&
  91. bytes.Contains(bartID, itemHash)
  92. },
  93. },
  94. },
  95. },
  96. },
  97. },
  98. expectOutput: wire.SNACMessage{
  99. Frame: wire.SNACFrame{
  100. FoodGroup: wire.BART,
  101. SubGroup: wire.BARTUploadReply,
  102. RequestID: 1234,
  103. },
  104. Body: wire.SNAC_0x10_0x03_BARTUploadReply{
  105. Code: wire.BARTReplyCodesSuccess,
  106. ID: wire.BARTID{
  107. Type: wire.BARTTypesBuddyIcon,
  108. BARTInfo: wire.BARTInfo{
  109. Flags: wire.BARTFlagsCustom | wire.BARTFlagsKnown,
  110. Hash: itemHash,
  111. },
  112. },
  113. },
  114. },
  115. instanceMatch: func(instance *state.SessionInstance) {
  116. have, hasIcon := instance.Session().BuddyIcon()
  117. assert.True(t, hasIcon)
  118. want := wire.BARTID{
  119. Type: wire.BARTTypesBuddyIcon,
  120. BARTInfo: wire.BARTInfo{
  121. Flags: wire.BARTFlagsCustom | wire.BARTFlagsKnown,
  122. Hash: itemHash,
  123. },
  124. }
  125. assert.Equal(t, want, have)
  126. },
  127. },
  128. {
  129. name: "insert existing buddy icon for current session",
  130. instance: newTestInstance("user_screen_name", sessOptBuddyIcon(wire.BARTID{
  131. Type: wire.BARTTypesBuddyIcon,
  132. BARTInfo: wire.BARTInfo{
  133. Flags: wire.BARTFlagsCustom | wire.BARTFlagsUnknown,
  134. Hash: itemHash,
  135. },
  136. })),
  137. inputSNAC: wire.SNACMessage{
  138. Frame: wire.SNACFrame{
  139. RequestID: 1234,
  140. },
  141. Body: wire.SNAC_0x10_0x02_BARTUploadQuery{
  142. Type: wire.BARTTypesBuddyIcon,
  143. Data: itemData,
  144. },
  145. },
  146. mockParams: mockParams{
  147. bartItemManagerParams: bartItemManagerParams{
  148. bartItemManagerUpsertParams: bartItemManagerUpsertParams{
  149. {
  150. itemHash: itemHash,
  151. payload: itemData,
  152. bartType: wire.BARTTypesBuddyIcon,
  153. err: state.ErrBARTItemExists,
  154. },
  155. },
  156. },
  157. buddyBroadcasterParams: buddyBroadcasterParams{
  158. broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
  159. {
  160. screenName: state.DisplayScreenName("user_screen_name"),
  161. bodyMatcher: func(tlvInfo wire.TLVUserInfo) bool {
  162. bartID, exists := tlvInfo.Bytes(wire.OServiceUserInfoBARTInfo)
  163. return exists &&
  164. tlvInfo.ScreenName == "user_screen_name" &&
  165. bytes.Contains(bartID, itemHash)
  166. },
  167. },
  168. },
  169. },
  170. messageRelayerParams: messageRelayerParams{
  171. relayToScreenNameParams: relayToScreenNameParams{
  172. {
  173. screenName: state.NewIdentScreenName("user_screen_name"),
  174. message: wire.SNACMessage{
  175. Frame: wire.SNACFrame{
  176. FoodGroup: wire.OService,
  177. SubGroup: wire.OServiceUserInfoUpdate,
  178. },
  179. Body: func(val any) bool {
  180. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  181. if !ok {
  182. return false
  183. }
  184. bartID, exists := snac.UserInfo[0].Bytes(wire.OServiceUserInfoBARTInfo)
  185. return exists &&
  186. snac.UserInfo[0].ScreenName == "user_screen_name" &&
  187. bytes.Contains(bartID, itemHash)
  188. },
  189. },
  190. },
  191. },
  192. },
  193. },
  194. expectOutput: wire.SNACMessage{
  195. Frame: wire.SNACFrame{
  196. FoodGroup: wire.BART,
  197. SubGroup: wire.BARTUploadReply,
  198. RequestID: 1234,
  199. },
  200. Body: wire.SNAC_0x10_0x03_BARTUploadReply{
  201. Code: wire.BARTReplyCodesSuccess,
  202. ID: wire.BARTID{
  203. Type: wire.BARTTypesBuddyIcon,
  204. BARTInfo: wire.BARTInfo{
  205. Flags: wire.BARTFlagsCustom | wire.BARTFlagsKnown,
  206. Hash: itemHash,
  207. },
  208. },
  209. },
  210. },
  211. instanceMatch: func(instance *state.SessionInstance) {
  212. have, hasIcon := instance.Session().BuddyIcon()
  213. assert.True(t, hasIcon)
  214. want := wire.BARTID{
  215. Type: wire.BARTTypesBuddyIcon,
  216. BARTInfo: wire.BARTInfo{
  217. Flags: wire.BARTFlagsCustom | wire.BARTFlagsKnown,
  218. Hash: itemHash,
  219. },
  220. }
  221. assert.Equal(t, want, have)
  222. },
  223. },
  224. {
  225. name: "insert new buddy icon, get insertion error",
  226. instance: newTestInstance("user_screen_name"),
  227. inputSNAC: wire.SNACMessage{
  228. Frame: wire.SNACFrame{
  229. RequestID: 1234,
  230. },
  231. Body: wire.SNAC_0x10_0x02_BARTUploadQuery{
  232. Type: wire.BARTTypesBuddyIcon,
  233. Data: itemData,
  234. },
  235. },
  236. mockParams: mockParams{
  237. bartItemManagerParams: bartItemManagerParams{
  238. bartItemManagerUpsertParams: bartItemManagerUpsertParams{
  239. {
  240. itemHash: itemHash,
  241. payload: itemData,
  242. bartType: wire.BARTTypesBuddyIcon,
  243. err: io.EOF,
  244. },
  245. },
  246. },
  247. },
  248. expectOutput: wire.SNACMessage{},
  249. wantErr: io.EOF,
  250. },
  251. {
  252. name: "insert new buddy icon unrelated to current session",
  253. instance: newTestInstance("user_screen_name", sessOptBuddyIcon(wire.BARTID{
  254. Type: wire.BARTTypesBuddyIcon,
  255. BARTInfo: wire.BARTInfo{
  256. Flags: wire.BARTFlagsCustom | wire.BARTFlagsKnown,
  257. Hash: []byte("unrelated icon"),
  258. },
  259. })),
  260. inputSNAC: wire.SNACMessage{
  261. Frame: wire.SNACFrame{
  262. RequestID: 1234,
  263. },
  264. Body: wire.SNAC_0x10_0x02_BARTUploadQuery{
  265. Type: wire.BARTTypesBuddyIcon,
  266. Data: itemData,
  267. },
  268. },
  269. mockParams: mockParams{
  270. bartItemManagerParams: bartItemManagerParams{
  271. bartItemManagerUpsertParams: bartItemManagerUpsertParams{
  272. {
  273. itemHash: itemHash,
  274. payload: itemData,
  275. bartType: wire.BARTTypesBuddyIcon,
  276. },
  277. },
  278. },
  279. messageRelayerParams: messageRelayerParams{},
  280. },
  281. expectOutput: wire.SNACMessage{
  282. Frame: wire.SNACFrame{
  283. FoodGroup: wire.BART,
  284. SubGroup: wire.BARTUploadReply,
  285. RequestID: 1234,
  286. },
  287. Body: wire.SNAC_0x10_0x03_BARTUploadReply{
  288. Code: wire.BARTReplyCodesSuccess,
  289. ID: wire.BARTID{
  290. Type: wire.BARTTypesBuddyIcon,
  291. BARTInfo: wire.BARTInfo{
  292. Flags: wire.BARTFlagsCustom,
  293. Hash: itemHash,
  294. },
  295. },
  296. },
  297. },
  298. instanceMatch: func(instance *state.SessionInstance) {
  299. // assert session icon didn't change
  300. have, hasIcon := instance.Session().BuddyIcon()
  301. assert.True(t, hasIcon)
  302. want := wire.BARTID{
  303. Type: wire.BARTTypesBuddyIcon,
  304. BARTInfo: wire.BARTInfo{
  305. Flags: wire.BARTFlagsCustom | wire.BARTFlagsKnown,
  306. Hash: []byte("unrelated icon"),
  307. },
  308. }
  309. assert.Equal(t, want, have)
  310. },
  311. },
  312. }
  313. for _, tc := range cases {
  314. t.Run(tc.name, func(t *testing.T) {
  315. bartItemManager := newMockBARTItemManager(t)
  316. for _, params := range tc.mockParams.bartItemManagerUpsertParams {
  317. bartItemManager.EXPECT().
  318. InsertBARTItem(matchContext(), params.itemHash, params.payload, params.bartType).
  319. Return(params.err)
  320. }
  321. buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
  322. for _, params := range tc.mockParams.broadcastBuddyArrivedParams {
  323. buddyUpdateBroadcaster.EXPECT().
  324. BroadcastBuddyArrived(mock.Anything,
  325. state.NewIdentScreenName(params.screenName.String()),
  326. mock.MatchedBy(params.bodyMatcher)).
  327. Return(params.err)
  328. }
  329. messageRelayer := newMockMessageRelayer(t)
  330. for _, params := range tc.mockParams.relayToScreenNameParams {
  331. messageRelayer.EXPECT().
  332. RelayToScreenName(matchContext(), params.screenName, mock.MatchedBy(func(message wire.SNACMessage) bool {
  333. return params.message.Frame == message.Frame &&
  334. params.message.Body.(func(any) bool)(message.Body)
  335. }))
  336. }
  337. svc := NewBARTService(slog.Default(), bartItemManager, messageRelayer, nil, nil)
  338. svc.buddyUpdateBroadcaster = buddyUpdateBroadcaster
  339. output, err := svc.UpsertItem(context.Background(), tc.instance, tc.inputSNAC.Frame,
  340. tc.inputSNAC.Body.(wire.SNAC_0x10_0x02_BARTUploadQuery))
  341. assert.ErrorIs(t, err, tc.wantErr)
  342. assert.Equal(t, output, tc.expectOutput)
  343. if tc.instanceMatch != nil {
  344. tc.instanceMatch(tc.instance)
  345. }
  346. })
  347. }
  348. }
  349. func TestBARTService_RetrieveItem(t *testing.T) {
  350. cases := []struct {
  351. // name is the unit test name
  352. name string
  353. // instance is the session of the user adding to feedbag
  354. instance *state.SessionInstance
  355. // inputSNAC is the SNAC sent from the client to the server
  356. inputSNAC wire.SNACMessage
  357. // mockParams is the list of params sent to mocks that satisfy this
  358. // method's dependencies
  359. mockParams mockParams
  360. // expectOutput is the SNAC sent from the server to client
  361. expectOutput wire.SNACMessage
  362. // expectErr is the expected error
  363. expectErr error
  364. }{
  365. {
  366. name: "retrieve buddy icon",
  367. instance: newTestInstance("user_screen_name"),
  368. inputSNAC: wire.SNACMessage{
  369. Frame: wire.SNACFrame{
  370. RequestID: 1234,
  371. },
  372. Body: wire.SNAC_0x10_0x04_BARTDownloadQuery{
  373. ScreenName: "user_screen_name",
  374. Command: 1,
  375. BARTID: wire.BARTID{
  376. Type: wire.BARTTypesBuddyIcon,
  377. BARTInfo: wire.BARTInfo{
  378. Flags: wire.BARTFlagsKnown,
  379. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  380. },
  381. },
  382. },
  383. },
  384. mockParams: mockParams{
  385. bartItemManagerParams: bartItemManagerParams{
  386. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  387. {
  388. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  389. result: []byte{'i', 't', 'e', 'm', 'd', 'a', 't', 'a'},
  390. },
  391. },
  392. },
  393. },
  394. expectOutput: wire.SNACMessage{
  395. Frame: wire.SNACFrame{
  396. FoodGroup: wire.BART,
  397. SubGroup: wire.BARTDownloadReply,
  398. RequestID: 1234,
  399. },
  400. Body: wire.SNAC_0x10_0x05_BARTDownloadReply{
  401. ScreenName: "user_screen_name",
  402. BARTID: wire.BARTID{
  403. Type: wire.BARTTypesBuddyIcon,
  404. BARTInfo: wire.BARTInfo{
  405. Flags: wire.BARTFlagsKnown,
  406. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  407. },
  408. },
  409. Data: []byte{'i', 't', 'e', 'm', 'd', 'a', 't', 'a'},
  410. },
  411. },
  412. },
  413. {
  414. name: "retrieve blank icon used for clearing buddy icon",
  415. instance: newTestInstance("user_screen_name"),
  416. inputSNAC: wire.SNACMessage{
  417. Frame: wire.SNACFrame{
  418. RequestID: 1234,
  419. },
  420. Body: wire.SNAC_0x10_0x04_BARTDownloadQuery{
  421. ScreenName: "user_screen_name",
  422. Command: 1,
  423. BARTID: wire.BARTID{
  424. Type: wire.BARTTypesBuddyIcon,
  425. BARTInfo: wire.BARTInfo{
  426. Flags: wire.BARTFlagsKnown,
  427. Hash: wire.GetClearIconHash(),
  428. },
  429. },
  430. },
  431. },
  432. expectOutput: wire.SNACMessage{
  433. Frame: wire.SNACFrame{
  434. FoodGroup: wire.BART,
  435. SubGroup: wire.BARTDownloadReply,
  436. RequestID: 1234,
  437. },
  438. Body: wire.SNAC_0x10_0x05_BARTDownloadReply{
  439. ScreenName: "user_screen_name",
  440. BARTID: wire.BARTID{
  441. Type: wire.BARTTypesBuddyIcon,
  442. BARTInfo: wire.BARTInfo{
  443. Flags: wire.BARTFlagsKnown,
  444. Hash: wire.GetClearIconHash(),
  445. },
  446. },
  447. Data: blankGIF,
  448. },
  449. },
  450. },
  451. {
  452. name: "retrieve item with error",
  453. instance: newTestInstance("user_screen_name"),
  454. inputSNAC: wire.SNACMessage{
  455. Frame: wire.SNACFrame{
  456. RequestID: 1234,
  457. },
  458. Body: wire.SNAC_0x10_0x04_BARTDownloadQuery{
  459. ScreenName: "user_screen_name",
  460. Command: 1,
  461. BARTID: wire.BARTID{
  462. Type: wire.BARTTypesBuddyIcon,
  463. BARTInfo: wire.BARTInfo{
  464. Flags: wire.BARTFlagsKnown,
  465. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  466. },
  467. },
  468. },
  469. },
  470. mockParams: mockParams{
  471. bartItemManagerParams: bartItemManagerParams{
  472. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  473. {
  474. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  475. result: nil,
  476. err: assert.AnError,
  477. },
  478. },
  479. },
  480. },
  481. expectOutput: wire.SNACMessage{},
  482. expectErr: assert.AnError,
  483. },
  484. }
  485. for _, tc := range cases {
  486. t.Run(tc.name, func(t *testing.T) {
  487. bartItemManager := newMockBARTItemManager(t)
  488. for _, params := range tc.mockParams.bartItemManagerRetrieveParams {
  489. bartItemManager.EXPECT().
  490. BARTItem(matchContext(), params.itemHash).
  491. Return(params.result, params.err)
  492. }
  493. svc := NewBARTService(slog.Default(), bartItemManager, nil, nil, nil)
  494. output, err := svc.RetrieveItem(context.Background(), tc.inputSNAC.Frame, tc.inputSNAC.Body.(wire.SNAC_0x10_0x04_BARTDownloadQuery))
  495. assert.ErrorIs(t, err, tc.expectErr)
  496. assert.Equal(t, output, tc.expectOutput)
  497. })
  498. }
  499. }
  500. func TestBARTService_RetrieveItemV2(t *testing.T) {
  501. cases := []struct {
  502. // name is the unit test name
  503. name string
  504. // instance is the session of the user adding to feedbag
  505. instance *state.SessionInstance
  506. // inputSNAC is the SNAC sent from the client to the server
  507. inputSNAC wire.SNACMessage
  508. // mockParams is the list of params sent to mocks that satisfy this
  509. // method's dependencies
  510. mockParams mockParams
  511. // expectOutput is the SNAC sent from the server to client
  512. expectOutput []wire.SNACMessage
  513. // expectErr is the expected error
  514. expectErr error
  515. }{
  516. {
  517. name: "retrieve single buddy icon",
  518. instance: newTestInstance("user_screen_name"),
  519. inputSNAC: wire.SNACMessage{
  520. Frame: wire.SNACFrame{
  521. RequestID: 1234,
  522. },
  523. Body: wire.SNAC_0x10_0x06_BARTDownload2Query{
  524. ScreenName: "user_screen_name",
  525. IDs: []wire.BARTID{
  526. {
  527. Type: wire.BARTTypesBuddyIcon,
  528. BARTInfo: wire.BARTInfo{
  529. Flags: wire.BARTFlagsKnown,
  530. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  531. },
  532. },
  533. },
  534. },
  535. },
  536. mockParams: mockParams{
  537. bartItemManagerParams: bartItemManagerParams{
  538. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  539. {
  540. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  541. result: []byte{'i', 't', 'e', 'm', 'd', 'a', 't', 'a'},
  542. },
  543. },
  544. },
  545. },
  546. expectOutput: []wire.SNACMessage{
  547. {
  548. Frame: wire.SNACFrame{
  549. FoodGroup: wire.BART,
  550. SubGroup: wire.BARTDownload2Reply,
  551. RequestID: 1234,
  552. },
  553. Body: wire.SNAC_0x10_0x07_BARTDownload2Reply{
  554. ScreenName: "user_screen_name",
  555. ReplyID: wire.BartQueryReplyID{
  556. QueryID: wire.BARTID{
  557. Type: wire.BARTTypesBuddyIcon,
  558. BARTInfo: wire.BARTInfo{
  559. Flags: wire.BARTFlagsKnown,
  560. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  561. },
  562. },
  563. Code: 0x00, // found
  564. ReplyID: wire.BARTID{
  565. Type: wire.BARTTypesBuddyIcon,
  566. BARTInfo: wire.BARTInfo{
  567. Flags: wire.BARTFlagsKnown,
  568. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  569. },
  570. },
  571. },
  572. Data: []byte{'i', 't', 'e', 'm', 'd', 'a', 't', 'a'},
  573. },
  574. },
  575. },
  576. },
  577. {
  578. name: "retrieve multiple buddy icons",
  579. instance: newTestInstance("user_screen_name"),
  580. inputSNAC: wire.SNACMessage{
  581. Frame: wire.SNACFrame{
  582. RequestID: 1234,
  583. },
  584. Body: wire.SNAC_0x10_0x06_BARTDownload2Query{
  585. ScreenName: "user_screen_name",
  586. IDs: []wire.BARTID{
  587. {
  588. Type: wire.BARTTypesBuddyIcon,
  589. BARTInfo: wire.BARTInfo{
  590. Flags: wire.BARTFlagsKnown,
  591. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  592. },
  593. },
  594. {
  595. Type: wire.BARTTypesBuddyIcon,
  596. BARTInfo: wire.BARTInfo{
  597. Flags: wire.BARTFlagsKnown,
  598. Hash: []byte{0x5f, 0xea, 0xd2, 0xa7, 0x56, 0xec, 0x6b, 0xfd, 0xec, 0x06, 0xd8, 0xb3, 0x5f, 0x9f, 0xb1, 0xfe},
  599. },
  600. },
  601. },
  602. },
  603. },
  604. mockParams: mockParams{
  605. bartItemManagerParams: bartItemManagerParams{
  606. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  607. {
  608. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  609. result: []byte{'f', 'i', 'r', 's', 't', 'i', 'c', 'o', 'n'},
  610. },
  611. {
  612. itemHash: []byte{0x5f, 0xea, 0xd2, 0xa7, 0x56, 0xec, 0x6b, 0xfd, 0xec, 0x06, 0xd8, 0xb3, 0x5f, 0x9f, 0xb1, 0xfe},
  613. result: []byte{'s', 'e', 'c', 'o', 'n', 'd', 'i', 'c', 'o', 'n'},
  614. },
  615. },
  616. },
  617. },
  618. expectOutput: []wire.SNACMessage{
  619. {
  620. Frame: wire.SNACFrame{
  621. FoodGroup: wire.BART,
  622. SubGroup: wire.BARTDownload2Reply,
  623. RequestID: 1234,
  624. },
  625. Body: wire.SNAC_0x10_0x07_BARTDownload2Reply{
  626. ScreenName: "user_screen_name",
  627. ReplyID: wire.BartQueryReplyID{
  628. QueryID: wire.BARTID{
  629. Type: wire.BARTTypesBuddyIcon,
  630. BARTInfo: wire.BARTInfo{
  631. Flags: wire.BARTFlagsKnown,
  632. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  633. },
  634. },
  635. Code: 0x00, // found
  636. ReplyID: wire.BARTID{
  637. Type: wire.BARTTypesBuddyIcon,
  638. BARTInfo: wire.BARTInfo{
  639. Flags: wire.BARTFlagsKnown,
  640. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  641. },
  642. },
  643. },
  644. Data: []byte{'f', 'i', 'r', 's', 't', 'i', 'c', 'o', 'n'},
  645. },
  646. },
  647. {
  648. Frame: wire.SNACFrame{
  649. FoodGroup: wire.BART,
  650. SubGroup: wire.BARTDownload2Reply,
  651. RequestID: 1234,
  652. },
  653. Body: wire.SNAC_0x10_0x07_BARTDownload2Reply{
  654. ScreenName: "user_screen_name",
  655. ReplyID: wire.BartQueryReplyID{
  656. QueryID: wire.BARTID{
  657. Type: wire.BARTTypesBuddyIcon,
  658. BARTInfo: wire.BARTInfo{
  659. Flags: wire.BARTFlagsKnown,
  660. Hash: []byte{0x5f, 0xea, 0xd2, 0xa7, 0x56, 0xec, 0x6b, 0xfd, 0xec, 0x06, 0xd8, 0xb3, 0x5f, 0x9f, 0xb1, 0xfe},
  661. },
  662. },
  663. Code: 0x00, // found
  664. ReplyID: wire.BARTID{
  665. Type: wire.BARTTypesBuddyIcon,
  666. BARTInfo: wire.BARTInfo{
  667. Flags: wire.BARTFlagsKnown,
  668. Hash: []byte{0x5f, 0xea, 0xd2, 0xa7, 0x56, 0xec, 0x6b, 0xfd, 0xec, 0x06, 0xd8, 0xb3, 0x5f, 0x9f, 0xb1, 0xfe},
  669. },
  670. },
  671. },
  672. Data: []byte{'s', 'e', 'c', 'o', 'n', 'd', 'i', 'c', 'o', 'n'},
  673. },
  674. },
  675. },
  676. },
  677. {
  678. name: "retrieve single item with error",
  679. instance: newTestInstance("user_screen_name"),
  680. inputSNAC: wire.SNACMessage{
  681. Frame: wire.SNACFrame{
  682. RequestID: 1234,
  683. },
  684. Body: wire.SNAC_0x10_0x06_BARTDownload2Query{
  685. ScreenName: "user_screen_name",
  686. IDs: []wire.BARTID{
  687. {
  688. Type: wire.BARTTypesBuddyIcon,
  689. BARTInfo: wire.BARTInfo{
  690. Flags: wire.BARTFlagsKnown,
  691. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  692. },
  693. },
  694. },
  695. },
  696. },
  697. mockParams: mockParams{
  698. bartItemManagerParams: bartItemManagerParams{
  699. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  700. {
  701. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  702. result: nil,
  703. err: assert.AnError,
  704. },
  705. },
  706. },
  707. },
  708. expectOutput: nil,
  709. expectErr: assert.AnError,
  710. },
  711. {
  712. name: "retrieve multiple items with error on second item",
  713. instance: newTestInstance("user_screen_name"),
  714. inputSNAC: wire.SNACMessage{
  715. Frame: wire.SNACFrame{
  716. RequestID: 1234,
  717. },
  718. Body: wire.SNAC_0x10_0x06_BARTDownload2Query{
  719. ScreenName: "user_screen_name",
  720. IDs: []wire.BARTID{
  721. {
  722. Type: wire.BARTTypesBuddyIcon,
  723. BARTInfo: wire.BARTInfo{
  724. Flags: wire.BARTFlagsKnown,
  725. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  726. },
  727. },
  728. {
  729. Type: wire.BARTTypesBuddyIcon,
  730. BARTInfo: wire.BARTInfo{
  731. Flags: wire.BARTFlagsKnown,
  732. Hash: []byte{0x5f, 0xea, 0xd2, 0xa7, 0x56, 0xec, 0x6b, 0xfd, 0xec, 0x06, 0xd8, 0xb3, 0x5f, 0x9f, 0xb1, 0xfe},
  733. },
  734. },
  735. },
  736. },
  737. },
  738. mockParams: mockParams{
  739. bartItemManagerParams: bartItemManagerParams{
  740. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  741. {
  742. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  743. result: []byte{'f', 'i', 'r', 's', 't', 'i', 'c', 'o', 'n'},
  744. err: nil,
  745. },
  746. {
  747. itemHash: []byte{0x5f, 0xea, 0xd2, 0xa7, 0x56, 0xec, 0x6b, 0xfd, 0xec, 0x06, 0xd8, 0xb3, 0x5f, 0x9f, 0xb1, 0xfe},
  748. result: nil,
  749. err: assert.AnError,
  750. },
  751. },
  752. },
  753. },
  754. expectOutput: nil,
  755. expectErr: assert.AnError,
  756. },
  757. {
  758. name: "retrieve mixed items (regular icon and clear icon)",
  759. instance: newTestInstance("user_screen_name"),
  760. inputSNAC: wire.SNACMessage{
  761. Frame: wire.SNACFrame{
  762. RequestID: 1234,
  763. },
  764. Body: wire.SNAC_0x10_0x06_BARTDownload2Query{
  765. ScreenName: "user_screen_name",
  766. IDs: []wire.BARTID{
  767. {
  768. Type: wire.BARTTypesBuddyIcon,
  769. BARTInfo: wire.BARTInfo{
  770. Flags: wire.BARTFlagsKnown,
  771. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  772. },
  773. },
  774. {
  775. Type: wire.BARTTypesBuddyIcon,
  776. BARTInfo: wire.BARTInfo{
  777. Flags: wire.BARTFlagsKnown,
  778. Hash: wire.GetClearIconHash(),
  779. },
  780. },
  781. },
  782. },
  783. },
  784. mockParams: mockParams{
  785. bartItemManagerParams: bartItemManagerParams{
  786. bartItemManagerRetrieveParams: bartItemManagerRetrieveParams{
  787. {
  788. itemHash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  789. result: []byte{'r', 'e', 'g', 'u', 'l', 'a', 'r', 'i', 'c', 'o', 'n'},
  790. },
  791. },
  792. },
  793. },
  794. expectOutput: []wire.SNACMessage{
  795. {
  796. Frame: wire.SNACFrame{
  797. FoodGroup: wire.BART,
  798. SubGroup: wire.BARTDownload2Reply,
  799. RequestID: 1234,
  800. },
  801. Body: wire.SNAC_0x10_0x07_BARTDownload2Reply{
  802. ScreenName: "user_screen_name",
  803. ReplyID: wire.BartQueryReplyID{
  804. QueryID: wire.BARTID{
  805. Type: wire.BARTTypesBuddyIcon,
  806. BARTInfo: wire.BARTInfo{
  807. Flags: wire.BARTFlagsKnown,
  808. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  809. },
  810. },
  811. Code: 0x00, // found
  812. ReplyID: wire.BARTID{
  813. Type: wire.BARTTypesBuddyIcon,
  814. BARTInfo: wire.BARTInfo{
  815. Flags: wire.BARTFlagsKnown,
  816. Hash: []byte{0x4e, 0xd9, 0xc1, 0x96, 0x45, 0xdb, 0x5a, 0xec, 0xdb, 0xf5, 0xc7, 0xa2, 0x4e, 0x8e, 0xa0, 0xed},
  817. },
  818. },
  819. },
  820. Data: []byte{'r', 'e', 'g', 'u', 'l', 'a', 'r', 'i', 'c', 'o', 'n'},
  821. },
  822. },
  823. {
  824. Frame: wire.SNACFrame{
  825. FoodGroup: wire.BART,
  826. SubGroup: wire.BARTDownload2Reply,
  827. RequestID: 1234,
  828. },
  829. Body: wire.SNAC_0x10_0x07_BARTDownload2Reply{
  830. ScreenName: "user_screen_name",
  831. ReplyID: wire.BartQueryReplyID{
  832. QueryID: wire.BARTID{
  833. Type: wire.BARTTypesBuddyIcon,
  834. BARTInfo: wire.BARTInfo{
  835. Flags: wire.BARTFlagsKnown,
  836. Hash: wire.GetClearIconHash(),
  837. },
  838. },
  839. Code: 0x00, // found
  840. ReplyID: wire.BARTID{
  841. Type: wire.BARTTypesBuddyIcon,
  842. BARTInfo: wire.BARTInfo{
  843. Flags: wire.BARTFlagsKnown,
  844. Hash: wire.GetClearIconHash(),
  845. },
  846. },
  847. },
  848. Data: blankGIF,
  849. },
  850. },
  851. },
  852. },
  853. }
  854. for _, tc := range cases {
  855. t.Run(tc.name, func(t *testing.T) {
  856. bartItemManager := newMockBARTItemManager(t)
  857. for _, params := range tc.mockParams.bartItemManagerRetrieveParams {
  858. bartItemManager.EXPECT().
  859. BARTItem(matchContext(), params.itemHash).
  860. Return(params.result, params.err)
  861. }
  862. svc := NewBARTService(slog.Default(), bartItemManager, nil, nil, nil)
  863. output, err := svc.RetrieveItemV2(context.Background(), tc.inputSNAC.Frame, tc.inputSNAC.Body.(wire.SNAC_0x10_0x06_BARTDownload2Query))
  864. assert.ErrorIs(t, err, tc.expectErr)
  865. assert.Equal(t, output, tc.expectOutput)
  866. })
  867. }
  868. }