cmd_server_test.go 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. package toc
  2. import (
  3. "context"
  4. "log/slog"
  5. "net"
  6. "sync"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/mock"
  10. "github.com/mk6i/open-oscar-server/state"
  11. "github.com/mk6i/open-oscar-server/wire"
  12. )
  13. func TestOSCARProxy_RecvBOS_ChatIn(t *testing.T) {
  14. cases := []struct {
  15. // name is the unit test name
  16. name string
  17. // me is the TOC user session
  18. me *state.SessionInstance
  19. // chatID is the chat ID
  20. chatID int
  21. // givenMsg is the incoming SNAC
  22. givenMsg wire.SNACMessage
  23. // wantCmd is the expected TOC response
  24. wantCmd string
  25. }{
  26. {
  27. name: "send chat message",
  28. me: newTestSession("me"),
  29. chatID: 0,
  30. givenMsg: wire.SNACMessage{
  31. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  32. TLVRestBlock: wire.TLVRestBlock{
  33. TLVList: wire.TLVList{
  34. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{
  35. ScreenName: "them",
  36. }),
  37. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  38. TLVList: wire.TLVList{
  39. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "<p>hello world!</p>"),
  40. },
  41. }),
  42. },
  43. },
  44. },
  45. },
  46. wantCmd: "CHAT_IN:0:them:F:<p>hello world!</p>",
  47. },
  48. }
  49. for _, tc := range cases {
  50. t.Run(tc.name, func(t *testing.T) {
  51. ctx, cancel := context.WithCancel(context.Background())
  52. svc := OSCARProxy{
  53. Logger: slog.Default(),
  54. }
  55. ch := make(chan []string)
  56. wg := &sync.WaitGroup{}
  57. wg.Add(1)
  58. go func() {
  59. defer wg.Done()
  60. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  61. }()
  62. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  63. assert.Equal(t, state.SessSendOK, status)
  64. gotCmd := <-ch
  65. assert.Equal(t, tc.wantCmd, gotCmd[0])
  66. cancel()
  67. wg.Wait()
  68. })
  69. }
  70. }
  71. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyArrived(t *testing.T) {
  72. cases := []struct {
  73. // name is the unit test name
  74. name string
  75. // me is the TOC user session
  76. me *state.SessionInstance
  77. // chatID is the chat ID
  78. chatID int
  79. // givenMsg is the incoming SNAC
  80. givenMsg wire.SNACMessage
  81. // wantCmd is the expected TOC response
  82. wantCmd []string
  83. }{
  84. {
  85. name: "send chat participant arrival",
  86. me: newTestSession("me"),
  87. givenMsg: wire.SNACMessage{
  88. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  89. Users: []wire.TLVUserInfo{
  90. {ScreenName: "user1"},
  91. {ScreenName: "user2"},
  92. },
  93. },
  94. },
  95. wantCmd: []string{"CHAT_UPDATE_BUDDY:0:T:user1:user2"},
  96. },
  97. }
  98. for _, tc := range cases {
  99. t.Run(tc.name, func(t *testing.T) {
  100. ctx, cancel := context.WithCancel(context.Background())
  101. svc := OSCARProxy{
  102. Logger: slog.Default(),
  103. }
  104. ch := make(chan []string)
  105. wg := &sync.WaitGroup{}
  106. wg.Add(1)
  107. go func() {
  108. defer wg.Done()
  109. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  110. }()
  111. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  112. assert.Equal(t, state.SessSendOK, status)
  113. gotCmd := <-ch
  114. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  115. cancel()
  116. wg.Wait()
  117. })
  118. }
  119. }
  120. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyLeft(t *testing.T) {
  121. cases := []struct {
  122. // name is the unit test name
  123. name string
  124. // me is the TOC user session
  125. me *state.SessionInstance
  126. // chatID is the chat ID
  127. chatID int
  128. // givenMsg is the incoming SNAC
  129. givenMsg wire.SNACMessage
  130. // wantCmd is the expected TOC response
  131. wantCmd []string
  132. }{
  133. {
  134. name: "send chat participant departure",
  135. me: newTestSession("me"),
  136. givenMsg: wire.SNACMessage{
  137. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{
  138. Users: []wire.TLVUserInfo{
  139. {ScreenName: "user1"},
  140. {ScreenName: "user2"},
  141. },
  142. },
  143. },
  144. wantCmd: []string{"CHAT_UPDATE_BUDDY:0:F:user1:user2"},
  145. },
  146. }
  147. for _, tc := range cases {
  148. t.Run(tc.name, func(t *testing.T) {
  149. ctx, cancel := context.WithCancel(context.Background())
  150. svc := OSCARProxy{
  151. Logger: slog.Default(),
  152. }
  153. ch := make(chan []string)
  154. wg := &sync.WaitGroup{}
  155. wg.Add(1)
  156. go func() {
  157. defer wg.Done()
  158. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  159. }()
  160. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  161. assert.Equal(t, state.SessSendOK, status)
  162. gotCmd := <-ch
  163. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  164. cancel()
  165. wg.Wait()
  166. })
  167. }
  168. }
  169. func TestOSCARProxy_RecvBOS_Eviled(t *testing.T) {
  170. cases := []struct {
  171. // name is the unit test name
  172. name string
  173. // me is the TOC user session
  174. me *state.SessionInstance
  175. // givenMsg is the incoming SNAC
  176. givenMsg wire.SNACMessage
  177. // chatRegistry is the chat registry for the current session
  178. chatRegistry *ChatRegistry
  179. // wantCmd is the expected TOC response
  180. wantCmd []string
  181. }{
  182. {
  183. name: "anonymous warning - 10%",
  184. me: newTestSession("me"),
  185. givenMsg: wire.SNACMessage{
  186. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  187. NewEvil: 100,
  188. },
  189. },
  190. wantCmd: []string{"EVILED:10:"},
  191. },
  192. {
  193. name: "normal warning - 10%",
  194. me: newTestSession("me"),
  195. givenMsg: wire.SNACMessage{
  196. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  197. NewEvil: 100,
  198. Snitcher: &struct {
  199. wire.TLVUserInfo
  200. }{
  201. TLVUserInfo: wire.TLVUserInfo{
  202. ScreenName: "them",
  203. },
  204. },
  205. },
  206. },
  207. wantCmd: []string{"EVILED:10:them"},
  208. },
  209. }
  210. for _, tc := range cases {
  211. t.Run(tc.name, func(t *testing.T) {
  212. ctx, cancel := context.WithCancel(context.Background())
  213. svc := testOSCARProxy(t)
  214. ch := make(chan []string)
  215. wg := &sync.WaitGroup{}
  216. wg.Add(1)
  217. go func() {
  218. defer wg.Done()
  219. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  220. assert.NoError(t, err)
  221. }()
  222. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  223. assert.Equal(t, state.SessSendOK, status)
  224. gotCmd := <-ch
  225. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  226. cancel()
  227. wg.Wait()
  228. })
  229. }
  230. }
  231. func TestOSCARProxy_RecvBOS_IMIn(t *testing.T) {
  232. cases := []struct {
  233. // name is the unit test name
  234. name string
  235. // me is the TOC user session
  236. me *state.SessionInstance
  237. // givenMsg is the incoming SNAC
  238. givenMsg wire.SNACMessage
  239. // wantCmd is the expected TOC response
  240. wantCmd []string
  241. }{
  242. {
  243. name: "send IM",
  244. me: newTestSession("me"),
  245. givenMsg: wire.SNACMessage{
  246. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  247. ChannelID: wire.ICBMChannelIM,
  248. TLVUserInfo: wire.TLVUserInfo{
  249. ScreenName: "them",
  250. },
  251. TLVRestBlock: wire.TLVRestBlock{
  252. TLVList: wire.TLVList{
  253. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  254. {
  255. ID: 0x5,
  256. Version: 0x1,
  257. Payload: []uint8{0x1, 0x1, 0x2},
  258. },
  259. {
  260. ID: 0x1,
  261. Version: 0x1,
  262. Payload: []uint8{
  263. 0x0, 0x0, // charset
  264. 0x0, 0x0, // lang
  265. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  266. },
  267. },
  268. }),
  269. },
  270. },
  271. },
  272. },
  273. wantCmd: []string{"IM_IN:them:F:hello world!"},
  274. },
  275. {
  276. name: "send IM - auto-response",
  277. me: newTestSession("me"),
  278. givenMsg: wire.SNACMessage{
  279. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  280. ChannelID: wire.ICBMChannelIM,
  281. TLVUserInfo: wire.TLVUserInfo{
  282. ScreenName: "them",
  283. },
  284. TLVRestBlock: wire.TLVRestBlock{
  285. TLVList: wire.TLVList{
  286. wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}),
  287. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  288. {
  289. ID: 0x5,
  290. Version: 0x1,
  291. Payload: []uint8{0x1, 0x1, 0x2},
  292. },
  293. {
  294. ID: 0x1,
  295. Version: 0x1,
  296. Payload: []uint8{
  297. 0x0, 0x0, // charset
  298. 0x0, 0x0, // lang
  299. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  300. },
  301. },
  302. }),
  303. },
  304. },
  305. },
  306. },
  307. wantCmd: []string{"IM_IN:them:T:hello world!"},
  308. },
  309. {
  310. name: "send IM - TOC2 (IM_IN2)",
  311. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  312. givenMsg: wire.SNACMessage{
  313. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  314. ChannelID: wire.ICBMChannelIM,
  315. TLVUserInfo: wire.TLVUserInfo{
  316. ScreenName: "them",
  317. },
  318. TLVRestBlock: wire.TLVRestBlock{
  319. TLVList: wire.TLVList{
  320. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  321. {ID: 0x5, Version: 0x1, Payload: []uint8{0x1, 0x1, 0x2}},
  322. {ID: 0x1, Version: 0x1, Payload: []uint8{0x0, 0x0, 0x0, 0x0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
  323. }),
  324. },
  325. },
  326. },
  327. },
  328. wantCmd: []string{"IM_IN2:them:F:F:hello world!"},
  329. },
  330. {
  331. name: "send IM - TOC2 with encoded messaging (IM_IN_ENC2)",
  332. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  333. givenMsg: wire.SNACMessage{
  334. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  335. ChannelID: wire.ICBMChannelIM,
  336. TLVUserInfo: wire.TLVUserInfo{
  337. ScreenName: "them",
  338. TLVBlock: wire.TLVBlock{
  339. TLVList: wire.TLVList{
  340. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  341. },
  342. },
  343. },
  344. TLVRestBlock: wire.TLVRestBlock{
  345. TLVList: wire.TLVList{
  346. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  347. {ID: 0x5, Version: 0x1, Payload: []uint8{0x1, 0x1, 0x2}},
  348. {ID: 0x1, Version: 0x1, Payload: []uint8{0x0, 0x0, 0x0, 0x0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
  349. }),
  350. },
  351. },
  352. },
  353. },
  354. wantCmd: []string{"IM_IN_ENC2:them:F:F:T: O :F:L:en:hello world!"},
  355. },
  356. {
  357. name: "send IM - TOC2 encoded messaging missing OServiceUserInfoUserFlags returns empty",
  358. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  359. givenMsg: wire.SNACMessage{
  360. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  361. ChannelID: wire.ICBMChannelIM,
  362. TLVUserInfo: wire.TLVUserInfo{
  363. ScreenName: "them",
  364. // no TLVList / OServiceUserInfoUserFlags
  365. },
  366. TLVRestBlock: wire.TLVRestBlock{
  367. TLVList: wire.TLVList{
  368. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  369. {ID: 0x5, Version: 0x1, Payload: []uint8{0x1, 0x1, 0x2}},
  370. {ID: 0x1, Version: 0x1, Payload: []uint8{0x0, 0x0, 0x0, 0x0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
  371. }),
  372. },
  373. },
  374. },
  375. },
  376. wantCmd: []string{""},
  377. },
  378. {
  379. name: "send chat invitation",
  380. me: newTestSession("me"),
  381. givenMsg: wire.SNACMessage{
  382. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  383. ChannelID: wire.ICBMChannelRendezvous,
  384. TLVUserInfo: wire.TLVUserInfo{
  385. ScreenName: "them",
  386. },
  387. TLVRestBlock: wire.TLVRestBlock{
  388. TLVList: wire.TLVList{
  389. wire.NewTLVBE(wire.ICBMTLVData, []wire.ICBMCh2Fragment{
  390. {
  391. Capability: wire.CapChat,
  392. TLVRestBlock: wire.TLVRestBlock{
  393. TLVList: wire.TLVList{
  394. wire.NewTLVBE(wire.ICBMRdvTLVTagsInvitation, "join my chat!"),
  395. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, wire.ICBMRoomInfo{
  396. Cookie: "a-b-the room",
  397. }),
  398. },
  399. },
  400. },
  401. }),
  402. },
  403. },
  404. },
  405. },
  406. wantCmd: []string{"CHAT_INVITE:the room:0:them:join my chat!"},
  407. },
  408. {
  409. name: "receive file transfer rendezvous IM",
  410. me: newTestSession("me"),
  411. givenMsg: wire.SNACMessage{
  412. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  413. ChannelID: wire.ICBMChannelRendezvous,
  414. TLVUserInfo: newTestSession("them").Session().TLVUserInfo(),
  415. TLVRestBlock: wire.TLVRestBlock{
  416. TLVList: wire.TLVList{
  417. wire.NewTLVBE(wire.ICBMTLVWantEvents, []byte{}),
  418. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  419. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  420. Type: wire.ICBMRdvMessagePropose,
  421. Capability: wire.CapFileTransfer,
  422. TLVRestBlock: wire.TLVRestBlock{
  423. TLVList: wire.TLVList{
  424. wire.NewTLVBE(wire.ICBMRdvTLVTagsSeqNum, uint16(1)),
  425. wire.NewTLVBE(wire.ICBMRdvTLVTagsPort, uint16(4000)),
  426. wire.NewTLVBE(wire.ICBMRdvTLVTagsRdvIP, net.ParseIP("129.168.0.1").To4()),
  427. wire.NewTLVBE(wire.ICBMRdvTLVTagsRequesterIP, net.ParseIP("129.168.0.2").To4()),
  428. wire.NewTLVBE(wire.ICBMRdvTLVTagsVerifiedIP, net.ParseIP("129.168.0.3").To4()),
  429. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, []byte{'l', 'o', 'l'}),
  430. },
  431. },
  432. }),
  433. },
  434. },
  435. },
  436. },
  437. wantCmd: []string{"RVOUS_PROPOSE:them:09461343-4C7F-11D1-8222-444553540000:aGFoYWhhaGE=:1:129.168.0.1:129.168.0.2:129.168.0.3:4000:10001:bG9s"},
  438. },
  439. }
  440. for _, tc := range cases {
  441. t.Run(tc.name, func(t *testing.T) {
  442. ctx, cancel := context.WithCancel(context.Background())
  443. svc := testOSCARProxy(t)
  444. ch := make(chan []string)
  445. wg := &sync.WaitGroup{}
  446. wg.Add(1)
  447. go func() {
  448. defer wg.Done()
  449. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  450. assert.NoError(t, err)
  451. }()
  452. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  453. assert.Equal(t, state.SessSendOK, status)
  454. gotCmd := <-ch
  455. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  456. cancel()
  457. wg.Wait()
  458. })
  459. }
  460. }
  461. func TestOSCARProxy_RecvBOS_UpdateBuddyArrival(t *testing.T) {
  462. cases := []struct {
  463. // name is the unit test name
  464. name string
  465. // me is the TOC user session
  466. me *state.SessionInstance
  467. // givenMsg is the incoming SNAC
  468. givenMsg wire.SNACMessage
  469. // wantCmd is the expected TOC response
  470. wantCmd []string
  471. }{
  472. {
  473. name: "send buddy arrival - buddy online",
  474. me: newTestSession("me"),
  475. givenMsg: wire.SNACMessage{
  476. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  477. TLVUserInfo: wire.TLVUserInfo{
  478. ScreenName: "me",
  479. WarningLevel: 0,
  480. TLVBlock: wire.TLVBlock{
  481. TLVList: wire.TLVList{
  482. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  483. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  484. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  485. },
  486. },
  487. },
  488. },
  489. },
  490. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: O ", ""},
  491. },
  492. {
  493. name: "send buddy arrival - buddy warned 10%",
  494. me: newTestSession("me"),
  495. givenMsg: wire.SNACMessage{
  496. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  497. TLVUserInfo: wire.TLVUserInfo{
  498. ScreenName: "me",
  499. WarningLevel: 100,
  500. TLVBlock: wire.TLVBlock{
  501. TLVList: wire.TLVList{
  502. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  503. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  504. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  505. },
  506. },
  507. },
  508. },
  509. },
  510. wantCmd: []string{"UPDATE_BUDDY:me:T:10:1234:5678: O ", ""},
  511. },
  512. {
  513. name: "send buddy arrival - buddy away",
  514. me: newTestSession("me"),
  515. givenMsg: wire.SNACMessage{
  516. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  517. TLVUserInfo: wire.TLVUserInfo{
  518. ScreenName: "me",
  519. WarningLevel: 0,
  520. TLVBlock: wire.TLVBlock{
  521. TLVList: wire.TLVList{
  522. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  523. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  524. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagUnavailable),
  525. },
  526. },
  527. },
  528. },
  529. },
  530. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: OU", ""},
  531. },
  532. {
  533. name: "send buddy arrival - user class AOL (userClassString uc[0])",
  534. me: newTestSession("me"),
  535. givenMsg: wire.SNACMessage{
  536. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  537. TLVUserInfo: wire.TLVUserInfo{
  538. ScreenName: "me",
  539. WarningLevel: 0,
  540. TLVBlock: wire.TLVBlock{
  541. TLVList: wire.TLVList{
  542. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  543. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  544. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagAOL),
  545. },
  546. },
  547. },
  548. },
  549. },
  550. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678:A ", ""},
  551. },
  552. {
  553. name: "send buddy arrival - user class Administrator (userClassString uc[1])",
  554. me: newTestSession("me"),
  555. givenMsg: wire.SNACMessage{
  556. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  557. TLVUserInfo: wire.TLVUserInfo{
  558. ScreenName: "me",
  559. WarningLevel: 0,
  560. TLVBlock: wire.TLVBlock{
  561. TLVList: wire.TLVList{
  562. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  563. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  564. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagAdministrator),
  565. },
  566. },
  567. },
  568. },
  569. },
  570. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: A ", ""},
  571. },
  572. {
  573. name: "send buddy arrival - user class Wireless (userClassString uc[1])",
  574. me: newTestSession("me"),
  575. givenMsg: wire.SNACMessage{
  576. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  577. TLVUserInfo: wire.TLVUserInfo{
  578. ScreenName: "me",
  579. WarningLevel: 0,
  580. TLVBlock: wire.TLVBlock{
  581. TLVList: wire.TLVList{
  582. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  583. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  584. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagWireless),
  585. },
  586. },
  587. },
  588. },
  589. },
  590. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: C ", ""},
  591. },
  592. {
  593. name: "send buddy arrival - user class Unconfirmed (userClassString uc[1])",
  594. me: newTestSession("me"),
  595. givenMsg: wire.SNACMessage{
  596. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  597. TLVUserInfo: wire.TLVUserInfo{
  598. ScreenName: "me",
  599. WarningLevel: 0,
  600. TLVBlock: wire.TLVBlock{
  601. TLVList: wire.TLVList{
  602. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  603. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  604. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagUnconfirmed),
  605. },
  606. },
  607. },
  608. },
  609. },
  610. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: U ", ""},
  611. },
  612. {
  613. name: "send buddy arrival - TOC2 no caps (userInfoToBuddyCaps returns empty when no OServiceUserInfoOscarCaps TLV)",
  614. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  615. givenMsg: wire.SNACMessage{
  616. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  617. TLVUserInfo: wire.TLVUserInfo{
  618. ScreenName: "buddy",
  619. WarningLevel: 0,
  620. TLVBlock: wire.TLVBlock{
  621. TLVList: wire.TLVList{
  622. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  623. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  624. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  625. },
  626. },
  627. },
  628. },
  629. },
  630. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :", ""},
  631. },
  632. {
  633. name: "send buddy arrival - TOC2 caps length not divisible by 16 (userInfoToBuddyCaps returns empty)",
  634. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  635. givenMsg: wire.SNACMessage{
  636. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  637. TLVUserInfo: wire.TLVUserInfo{
  638. ScreenName: "buddy",
  639. WarningLevel: 0,
  640. TLVBlock: wire.TLVBlock{
  641. TLVList: wire.TLVList{
  642. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  643. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  644. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  645. // Invalid: 8 bytes, not divisible by 16
  646. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4}),
  647. },
  648. },
  649. },
  650. },
  651. },
  652. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :", ""},
  653. },
  654. {
  655. name: "send buddy arrival - TOC2 with one capability (userInfoToBuddyCaps formats caps as UUIDs)",
  656. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  657. givenMsg: wire.SNACMessage{
  658. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  659. TLVUserInfo: wire.TLVUserInfo{
  660. ScreenName: "buddy",
  661. WarningLevel: 0,
  662. TLVBlock: wire.TLVBlock{
  663. TLVList: wire.TLVList{
  664. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  665. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  666. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  667. // One 16-byte cap: UUID 550e8400-e29b-41d4-a716-446655440000
  668. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{
  669. 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4,
  670. 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
  671. }),
  672. },
  673. },
  674. },
  675. },
  676. },
  677. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :", "BUDDY_CAPS2:buddy:550e8400-e29b-41d4-a716-446655440000"},
  678. },
  679. {
  680. name: "send buddy arrival - TOC2 with two capabilities",
  681. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  682. givenMsg: wire.SNACMessage{
  683. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  684. TLVUserInfo: wire.TLVUserInfo{
  685. ScreenName: "buddy",
  686. WarningLevel: 0,
  687. TLVBlock: wire.TLVBlock{
  688. TLVList: wire.TLVList{
  689. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  690. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  691. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  692. // Two 16-byte caps
  693. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{
  694. 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
  695. 0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00,
  696. }),
  697. },
  698. },
  699. },
  700. },
  701. },
  702. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :", "BUDDY_CAPS2:buddy:550e8400-e29b-41d4-a716-446655440000,748f2420-6287-11d1-8222-444553540000"},
  703. },
  704. }
  705. for _, tc := range cases {
  706. t.Run(tc.name, func(t *testing.T) {
  707. ctx, cancel := context.WithCancel(context.Background())
  708. svc := testOSCARProxy(t)
  709. ch := make(chan []string)
  710. wg := &sync.WaitGroup{}
  711. wg.Add(1)
  712. go func() {
  713. defer wg.Done()
  714. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  715. assert.NoError(t, err)
  716. }()
  717. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  718. assert.Equal(t, state.SessSendOK, status)
  719. gotCmd := <-ch
  720. assert.Len(t, gotCmd, len(tc.wantCmd), "UpdateBuddyArrival returns UPDATE_BUDDY line and BUDDY_CAPS2 line (empty for non-TOC2)")
  721. for i, want := range tc.wantCmd {
  722. assert.Equal(t, want, gotCmd[i])
  723. }
  724. cancel()
  725. wg.Wait()
  726. })
  727. }
  728. }
  729. func TestOSCARProxy_RecvBOS_UpdateBuddyDeparted(t *testing.T) {
  730. cases := []struct {
  731. // name is the unit test name
  732. name string
  733. // me is the TOC user session
  734. me *state.SessionInstance
  735. // givenMsg is the incoming SNAC
  736. givenMsg wire.SNACMessage
  737. // wantCmd is the expected TOC response
  738. wantCmd []string
  739. }{
  740. {
  741. name: "send buddy departure TOC1",
  742. me: newTestSession("me"),
  743. givenMsg: wire.SNACMessage{
  744. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{
  745. TLVUserInfo: wire.TLVUserInfo{
  746. ScreenName: "me",
  747. },
  748. },
  749. },
  750. wantCmd: []string{"UPDATE_BUDDY:me:F:0:0:0: "},
  751. },
  752. {
  753. name: "send buddy departure TOC2",
  754. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  755. givenMsg: wire.SNACMessage{
  756. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{
  757. TLVUserInfo: wire.TLVUserInfo{
  758. ScreenName: "me",
  759. },
  760. },
  761. },
  762. wantCmd: []string{"UPDATE_BUDDY2:me:F:0:0:0: :"},
  763. },
  764. }
  765. for _, tc := range cases {
  766. t.Run(tc.name, func(t *testing.T) {
  767. ctx, cancel := context.WithCancel(context.Background())
  768. svc := testOSCARProxy(t)
  769. ch := make(chan []string)
  770. wg := &sync.WaitGroup{}
  771. wg.Add(1)
  772. go func() {
  773. defer wg.Done()
  774. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  775. assert.NoError(t, err)
  776. }()
  777. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  778. assert.Equal(t, state.SessSendOK, status)
  779. gotCmd := <-ch
  780. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  781. cancel()
  782. wg.Wait()
  783. })
  784. }
  785. }
  786. func TestOSCARProxy_RecvBOS_ClientEvent(t *testing.T) {
  787. cases := []struct {
  788. name string
  789. me *state.SessionInstance
  790. givenMsg wire.SNACMessage
  791. wantCmd []string
  792. }{
  793. {
  794. name: "TOC2 client receives CLIENT_EVENT2 (typing event)",
  795. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  796. givenMsg: wire.SNACMessage{
  797. Body: wire.SNAC_0x04_0x14_ICBMClientEvent{
  798. ScreenName: "buddy",
  799. Event: 1, // typing
  800. },
  801. },
  802. wantCmd: []string{"CLIENT_EVENT2:buddy:1"},
  803. },
  804. {
  805. name: "TOC2 client receives CLIENT_EVENT2 event 0 (idle)",
  806. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  807. givenMsg: wire.SNACMessage{
  808. Body: wire.SNAC_0x04_0x14_ICBMClientEvent{
  809. ScreenName: "alice",
  810. Event: 0,
  811. },
  812. },
  813. wantCmd: []string{"CLIENT_EVENT2:alice:0"},
  814. },
  815. {
  816. name: "TOC2 client receives CLIENT_EVENT2 event 2 (entered text)",
  817. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  818. givenMsg: wire.SNACMessage{
  819. Body: wire.SNAC_0x04_0x14_ICBMClientEvent{
  820. ScreenName: "bob",
  821. Event: 2,
  822. },
  823. },
  824. wantCmd: []string{"CLIENT_EVENT2:bob:2"},
  825. },
  826. }
  827. for _, tc := range cases {
  828. t.Run(tc.name, func(t *testing.T) {
  829. ctx, cancel := context.WithCancel(context.Background())
  830. svc := testOSCARProxy(t)
  831. ch := make(chan []string)
  832. wg := &sync.WaitGroup{}
  833. wg.Add(1)
  834. go func() {
  835. defer wg.Done()
  836. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  837. assert.NoError(t, err)
  838. }()
  839. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  840. assert.Equal(t, state.SessSendOK, status)
  841. gotCmd := <-ch
  842. assert.Equal(t, tc.wantCmd, gotCmd)
  843. cancel()
  844. wg.Wait()
  845. })
  846. }
  847. }
  848. func TestOSCARProxy_RecvBOS_Signout(t *testing.T) {
  849. }
  850. func TestOSCARProxy_Inserted2(t *testing.T) {
  851. ctx := context.Background()
  852. me := newTestSession("me")
  853. buddyWithAlias := wire.FeedbagItem{
  854. ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice",
  855. TLVLBlock: wire.TLVLBlock{},
  856. }
  857. buddyWithAlias.Append(wire.NewTLVBE(wire.FeedbagAttributesAlias, []byte("Alice N.")))
  858. cases := []struct {
  859. name string
  860. snac wire.SNAC_0x13_0x09_FeedbagUpdateItem
  861. wantCmd []string
  862. mockParams mockParams
  863. }{
  864. {
  865. name: "one buddy - group name from feedbag",
  866. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  867. Items: []wire.FeedbagItem{
  868. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  869. },
  870. },
  871. wantCmd: []string{"INSERTED2:b::alice:Work"},
  872. mockParams: mockParams{
  873. feedBagParams: feedBagParams{
  874. feedbagParams: feedbagParams{
  875. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"}}, err: nil},
  876. },
  877. },
  878. },
  879. },
  880. {
  881. name: "one buddy with alias",
  882. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  883. Items: []wire.FeedbagItem{buddyWithAlias},
  884. },
  885. wantCmd: []string{"INSERTED2:b:Alice N.:alice:Work"},
  886. mockParams: mockParams{
  887. feedBagParams: feedBagParams{
  888. feedbagParams: feedbagParams{
  889. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"}}, err: nil},
  890. },
  891. },
  892. },
  893. },
  894. {
  895. name: "one buddy - unknown group ID uses Buddies",
  896. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  897. Items: []wire.FeedbagItem{
  898. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 999, Name: "bob"},
  899. },
  900. },
  901. wantCmd: []string{"INSERTED2:b::bob:Buddies"},
  902. mockParams: mockParams{
  903. feedBagParams: feedBagParams{
  904. feedbagParams: feedbagParams{
  905. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{}, err: nil},
  906. },
  907. },
  908. },
  909. },
  910. {
  911. name: "two buddies same group",
  912. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  913. Items: []wire.FeedbagItem{
  914. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  915. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob"},
  916. },
  917. },
  918. wantCmd: []string{"INSERTED2:b::alice:Friends", "INSERTED2:b::bob:Friends"},
  919. mockParams: mockParams{
  920. feedBagParams: feedBagParams{
  921. feedbagParams: feedbagParams{
  922. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Friends"}}, err: nil},
  923. },
  924. },
  925. },
  926. },
  927. {
  928. name: "group added",
  929. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  930. Items: []wire.FeedbagItem{
  931. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "NewGroup"},
  932. },
  933. },
  934. wantCmd: []string{"INSERTED2:g:NewGroup"},
  935. mockParams: mockParams{},
  936. },
  937. {
  938. name: "permit and deny added",
  939. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  940. Items: []wire.FeedbagItem{
  941. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice"},
  942. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob"},
  943. },
  944. },
  945. wantCmd: []string{"INSERTED2:p:alice", "INSERTED2:d:bob"},
  946. mockParams: mockParams{},
  947. },
  948. {
  949. name: "all four types - group buddy deny permit",
  950. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  951. Items: []wire.FeedbagItem{
  952. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 200, Name: "NewGroup"},
  953. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  954. {ItemID: 3, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "blockedUser"},
  955. {ItemID: 4, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "allowedUser"},
  956. },
  957. },
  958. wantCmd: []string{"INSERTED2:g:NewGroup", "INSERTED2:b::alice:Buddies", "INSERTED2:d:blockedUser", "INSERTED2:p:allowedUser"},
  959. mockParams: mockParams{
  960. feedBagParams: feedBagParams{
  961. feedbagParams: feedbagParams{
  962. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Buddies"}}, err: nil},
  963. },
  964. },
  965. },
  966. },
  967. {
  968. name: "feedbag lookup fails",
  969. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  970. Items: []wire.FeedbagItem{
  971. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  972. },
  973. },
  974. wantCmd: nil,
  975. mockParams: mockParams{
  976. feedBagParams: feedBagParams{
  977. feedbagParams: feedbagParams{
  978. {screenName: state.NewIdentScreenName("me"), results: nil, err: assert.AnError},
  979. },
  980. },
  981. },
  982. },
  983. }
  984. for _, tc := range cases {
  985. t.Run(tc.name, func(t *testing.T) {
  986. fbMgr := newMockFeedbagManager(t)
  987. for _, params := range tc.mockParams.feedBagParams.feedbagParams {
  988. fbMgr.EXPECT().
  989. Feedbag(mock.Anything, params.screenName).
  990. Return(params.results, params.err)
  991. }
  992. svc := OSCARProxy{
  993. Logger: slog.Default(),
  994. FeedbagManager: fbMgr,
  995. }
  996. got := svc.Inserted2(ctx, me, tc.snac)
  997. assert.Equal(t, tc.wantCmd, got)
  998. })
  999. }
  1000. }
  1001. func TestOSCARProxy_Deleted2(t *testing.T) {
  1002. ctx := context.Background()
  1003. me := newTestSession("me")
  1004. cases := []struct {
  1005. name string
  1006. snac wire.SNAC_0x13_0x0A_FeedbagDeleteItem
  1007. wantCmd []string
  1008. mockParams mockParams
  1009. }{
  1010. {
  1011. name: "one buddy removed",
  1012. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1013. Items: []wire.FeedbagItem{
  1014. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1015. },
  1016. },
  1017. wantCmd: []string{"DELETED2:b:alice:Buddies"},
  1018. mockParams: mockParams{
  1019. feedBagParams: feedBagParams{
  1020. feedbagParams: feedbagParams{
  1021. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Buddies"}}, err: nil},
  1022. },
  1023. },
  1024. },
  1025. },
  1026. {
  1027. name: "two buddies removed",
  1028. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1029. Items: []wire.FeedbagItem{
  1030. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1031. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob"},
  1032. },
  1033. },
  1034. wantCmd: []string{"DELETED2:b:alice:Friends", "DELETED2:b:bob:Friends"},
  1035. mockParams: mockParams{
  1036. feedBagParams: feedBagParams{
  1037. feedbagParams: feedbagParams{
  1038. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Friends"}}, err: nil},
  1039. },
  1040. },
  1041. },
  1042. },
  1043. {
  1044. name: "one buddy - unknown group ID uses Buddies",
  1045. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1046. Items: []wire.FeedbagItem{
  1047. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 999, Name: "alice"},
  1048. },
  1049. },
  1050. wantCmd: []string{"DELETED2:b:alice:Buddies"},
  1051. mockParams: mockParams{
  1052. feedBagParams: feedBagParams{
  1053. feedbagParams: feedbagParams{
  1054. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{}, err: nil},
  1055. },
  1056. },
  1057. },
  1058. },
  1059. {
  1060. name: "permit and deny removed",
  1061. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1062. Items: []wire.FeedbagItem{
  1063. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice"},
  1064. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob"},
  1065. },
  1066. },
  1067. wantCmd: []string{"DELETED2:p:alice", "DELETED2:d:bob"},
  1068. mockParams: mockParams{},
  1069. },
  1070. {
  1071. name: "group deleted",
  1072. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1073. Items: []wire.FeedbagItem{
  1074. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"},
  1075. },
  1076. },
  1077. wantCmd: []string{"DELETED2:g:Work"},
  1078. mockParams: mockParams{},
  1079. },
  1080. {
  1081. name: "mix of buddy permit and deny emitted",
  1082. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1083. Items: []wire.FeedbagItem{
  1084. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "permitUser"},
  1085. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "buddyUser"},
  1086. },
  1087. },
  1088. wantCmd: []string{"DELETED2:p:permitUser", "DELETED2:b:buddyUser:Work"},
  1089. mockParams: mockParams{
  1090. feedBagParams: feedBagParams{
  1091. feedbagParams: feedbagParams{
  1092. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"}}, err: nil},
  1093. },
  1094. },
  1095. },
  1096. },
  1097. {
  1098. name: "all four types - group buddy deny permit",
  1099. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1100. Items: []wire.FeedbagItem{
  1101. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 200, Name: "OldGroup"},
  1102. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1103. {ItemID: 3, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "blockedUser"},
  1104. {ItemID: 4, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "allowedUser"},
  1105. },
  1106. },
  1107. wantCmd: []string{"DELETED2:g:OldGroup", "DELETED2:b:alice:Buddies", "DELETED2:d:blockedUser", "DELETED2:p:allowedUser"},
  1108. mockParams: mockParams{
  1109. feedBagParams: feedBagParams{
  1110. feedbagParams: feedbagParams{
  1111. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Buddies"}}, err: nil},
  1112. },
  1113. },
  1114. },
  1115. },
  1116. {
  1117. name: "empty items",
  1118. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{Items: nil},
  1119. wantCmd: nil,
  1120. mockParams: mockParams{},
  1121. },
  1122. {
  1123. name: "feedbag lookup fails",
  1124. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1125. Items: []wire.FeedbagItem{
  1126. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1127. },
  1128. },
  1129. wantCmd: nil,
  1130. mockParams: mockParams{
  1131. feedBagParams: feedBagParams{
  1132. feedbagParams: feedbagParams{
  1133. {screenName: state.NewIdentScreenName("me"), results: nil, err: assert.AnError},
  1134. },
  1135. },
  1136. },
  1137. },
  1138. }
  1139. for _, tc := range cases {
  1140. t.Run(tc.name, func(t *testing.T) {
  1141. fbMgr := newMockFeedbagManager(t)
  1142. for _, params := range tc.mockParams.feedBagParams.feedbagParams {
  1143. fbMgr.EXPECT().
  1144. Feedbag(mock.Anything, params.screenName).
  1145. Return(params.results, params.err)
  1146. }
  1147. svc := OSCARProxy{
  1148. Logger: slog.Default(),
  1149. FeedbagManager: fbMgr,
  1150. }
  1151. got := svc.Deleted2(ctx, me, tc.snac)
  1152. assert.Equal(t, tc.wantCmd, got)
  1153. })
  1154. }
  1155. }
  1156. func testOSCARProxy(t *testing.T) OSCARProxy {
  1157. buddyService := newMockBuddyService(t)
  1158. buddyService.EXPECT().
  1159. BroadcastBuddyDeparted(mock.Anything, mock.Anything).
  1160. Maybe().
  1161. Return(nil)
  1162. buddyListRegistry := newMockBuddyListRegistry(t)
  1163. buddyListRegistry.EXPECT().
  1164. UnregisterBuddyList(mock.Anything, mock.Anything).
  1165. Maybe().
  1166. Return(nil)
  1167. authService := newMockAuthService(t)
  1168. authService.EXPECT().
  1169. Signout(mock.Anything, mock.Anything).
  1170. Maybe()
  1171. authService.EXPECT().
  1172. SignoutChat(mock.Anything, mock.Anything).
  1173. Maybe()
  1174. return OSCARProxy{
  1175. AuthService: authService,
  1176. BuddyListRegistry: buddyListRegistry,
  1177. BuddyService: buddyService,
  1178. Logger: slog.Default(),
  1179. }
  1180. }