locate_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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. // newTestSession returns a copy of the prototype session with fixed time
  11. //
  12. //goland:noinspection GoVetCopyLock
  13. func newTestSession(prototype Session) *Session {
  14. prototype.SignonTime = time.UnixMilli(1696790127565)
  15. return &prototype
  16. }
  17. func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
  18. cases := []struct {
  19. // name is the unit test name
  20. name string
  21. // blockedState is the response to the sender/recipient block check
  22. blockedState BlockedState
  23. // screenNameLookups is the list of user session lookups
  24. screenNameLookups map[string]struct {
  25. sess *Session
  26. err error
  27. }
  28. // screenNameLookups is the list of user session lookups
  29. profileLookups map[string]struct {
  30. payload string
  31. err error
  32. }
  33. // userSession is the session of the user requesting the user info
  34. userSession *Session
  35. // inputSNAC is the SNAC sent by the sender client
  36. inputSNAC oscar.SNAC_0x02_0x15_LocateUserInfoQuery2
  37. expectOutput XMessage
  38. }{
  39. {
  40. name: "request user info, expect user info response",
  41. blockedState: BlockedNo,
  42. screenNameLookups: map[string]struct {
  43. sess *Session
  44. err error
  45. }{
  46. "requested-user": {
  47. sess: newTestSession(Session{
  48. ScreenName: "requested-user",
  49. AwayMessage: "this is my away message!",
  50. }),
  51. },
  52. },
  53. userSession: &Session{
  54. ScreenName: "user_screen_name",
  55. },
  56. inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  57. Type2: 0,
  58. ScreenName: "requested-user",
  59. },
  60. expectOutput: XMessage{
  61. snacFrame: oscar.SnacFrame{
  62. FoodGroup: oscar.LOCATE,
  63. SubGroup: oscar.LocateUserInfoReply,
  64. },
  65. snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
  66. TLVUserInfo: newTestSession(Session{
  67. ScreenName: "requested-user",
  68. AwayMessage: "this is my away message!",
  69. }).GetTLVUserInfo(),
  70. LocateInfo: oscar.TLVRestBlock{},
  71. },
  72. },
  73. },
  74. {
  75. name: "request user info + profile, expect user info response + profile",
  76. blockedState: BlockedNo,
  77. screenNameLookups: map[string]struct {
  78. sess *Session
  79. err error
  80. }{
  81. "requested-user": {
  82. sess: newTestSession(Session{
  83. ScreenName: "requested-user",
  84. AwayMessage: "this is my away message!",
  85. }),
  86. },
  87. },
  88. profileLookups: map[string]struct {
  89. payload string
  90. err error
  91. }{
  92. "requested-user": {
  93. payload: "this is my profile!",
  94. },
  95. },
  96. userSession: &Session{
  97. ScreenName: "user_screen_name",
  98. },
  99. inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  100. // 2048 is a dummy to make sure bitmask check works
  101. Type2: oscar.LocateType2Sig | 2048,
  102. ScreenName: "requested-user",
  103. },
  104. expectOutput: XMessage{
  105. snacFrame: oscar.SnacFrame{
  106. FoodGroup: oscar.LOCATE,
  107. SubGroup: oscar.LocateUserInfoReply,
  108. },
  109. snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
  110. TLVUserInfo: newTestSession(Session{
  111. ScreenName: "requested-user",
  112. AwayMessage: "this is my away message!",
  113. }).GetTLVUserInfo(),
  114. LocateInfo: oscar.TLVRestBlock{
  115. TLVList: oscar.TLVList{
  116. {
  117. TType: oscar.LocateTLVTagsInfoSigMime,
  118. Val: `text/aolrtf; charset="us-ascii"`,
  119. },
  120. {
  121. TType: oscar.LocateTLVTagsInfoSigData,
  122. Val: "this is my profile!",
  123. },
  124. },
  125. },
  126. },
  127. },
  128. },
  129. {
  130. name: "request user info + profile, expect user info response + profile",
  131. blockedState: BlockedNo,
  132. screenNameLookups: map[string]struct {
  133. sess *Session
  134. err error
  135. }{
  136. "requested-user": {
  137. sess: newTestSession(Session{
  138. ScreenName: "requested-user",
  139. AwayMessage: "this is my away message!",
  140. }),
  141. },
  142. },
  143. profileLookups: map[string]struct {
  144. payload string
  145. err error
  146. }{
  147. "requested-user": {
  148. payload: "this is my profile!",
  149. },
  150. },
  151. userSession: &Session{
  152. ScreenName: "user_screen_name",
  153. },
  154. inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  155. // 2048 is a dummy to make sure bitmask check works
  156. Type2: oscar.LocateType2Sig | 2048,
  157. ScreenName: "requested-user",
  158. },
  159. expectOutput: XMessage{
  160. snacFrame: oscar.SnacFrame{
  161. FoodGroup: oscar.LOCATE,
  162. SubGroup: oscar.LocateUserInfoReply,
  163. },
  164. snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
  165. TLVUserInfo: newTestSession(Session{
  166. ScreenName: "requested-user",
  167. AwayMessage: "this is my away message!",
  168. }).GetTLVUserInfo(),
  169. LocateInfo: oscar.TLVRestBlock{
  170. TLVList: oscar.TLVList{
  171. {
  172. TType: oscar.LocateTLVTagsInfoSigMime,
  173. Val: `text/aolrtf; charset="us-ascii"`,
  174. },
  175. {
  176. TType: oscar.LocateTLVTagsInfoSigData,
  177. Val: "this is my profile!",
  178. },
  179. },
  180. },
  181. },
  182. },
  183. },
  184. {
  185. name: "request user info + away message, expect user info response + away message",
  186. blockedState: BlockedNo,
  187. screenNameLookups: map[string]struct {
  188. sess *Session
  189. err error
  190. }{
  191. "requested-user": {
  192. sess: newTestSession(Session{
  193. ScreenName: "requested-user",
  194. AwayMessage: "this is my away message!",
  195. }),
  196. },
  197. },
  198. userSession: &Session{
  199. ScreenName: "user_screen_name",
  200. },
  201. inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  202. // 2048 is a dummy to make sure bitmask check works
  203. Type2: oscar.LocateType2Unavailable | 2048,
  204. ScreenName: "requested-user",
  205. },
  206. expectOutput: XMessage{
  207. snacFrame: oscar.SnacFrame{
  208. FoodGroup: oscar.LOCATE,
  209. SubGroup: oscar.LocateUserInfoReply,
  210. },
  211. snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
  212. TLVUserInfo: newTestSession(Session{
  213. ScreenName: "requested-user",
  214. AwayMessage: "this is my away message!",
  215. }).GetTLVUserInfo(),
  216. LocateInfo: oscar.TLVRestBlock{
  217. TLVList: oscar.TLVList{
  218. {
  219. TType: oscar.LocateTLVTagsInfoUnavailableMime,
  220. Val: `text/aolrtf; charset="us-ascii"`,
  221. },
  222. {
  223. TType: oscar.LocateTLVTagsInfoUnavailableData,
  224. Val: "this is my away message!",
  225. },
  226. },
  227. },
  228. },
  229. },
  230. },
  231. {
  232. name: "request user info of user who blocked requester, expect not logged in error",
  233. blockedState: BlockedB,
  234. userSession: &Session{
  235. ScreenName: "user_screen_name",
  236. },
  237. inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  238. ScreenName: "requested-user",
  239. },
  240. expectOutput: XMessage{
  241. snacFrame: oscar.SnacFrame{
  242. FoodGroup: oscar.LOCATE,
  243. SubGroup: oscar.LocateErr,
  244. },
  245. snacOut: oscar.SnacError{
  246. Code: ErrorCodeNotLoggedOn,
  247. },
  248. },
  249. },
  250. {
  251. name: "request user info of user who does not exist, expect not logged in error",
  252. blockedState: BlockedNo,
  253. screenNameLookups: map[string]struct {
  254. sess *Session
  255. err error
  256. }{
  257. "non_existent_requested_user": {
  258. err: ErrSessNotFound,
  259. },
  260. },
  261. userSession: &Session{
  262. ScreenName: "user_screen_name",
  263. },
  264. inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  265. ScreenName: "non_existent_requested_user",
  266. },
  267. expectOutput: XMessage{
  268. snacFrame: oscar.SnacFrame{
  269. FoodGroup: oscar.LOCATE,
  270. SubGroup: oscar.LocateErr,
  271. },
  272. snacOut: oscar.SnacError{
  273. Code: ErrorCodeNotLoggedOn,
  274. },
  275. },
  276. },
  277. }
  278. for _, tc := range cases {
  279. t.Run(tc.name, func(t *testing.T) {
  280. fm := NewMockFeedbagManager(t)
  281. fm.EXPECT().
  282. Blocked(tc.userSession.ScreenName, tc.inputSNAC.ScreenName).
  283. Return(tc.blockedState, nil).
  284. Maybe()
  285. sm := NewMockSessionManager(t)
  286. for screenName, val := range tc.screenNameLookups {
  287. sm.EXPECT().
  288. RetrieveByScreenName(screenName).
  289. Return(val.sess, val.err).
  290. Maybe()
  291. }
  292. pm := NewMockProfileManager(t)
  293. for screenName, val := range tc.profileLookups {
  294. pm.EXPECT().
  295. RetrieveProfile(screenName).
  296. Return(val.payload, val.err).
  297. Maybe()
  298. }
  299. svc := LocateService{}
  300. outputSNAC, err := svc.UserInfoQuery2Handler(tc.userSession, sm, fm, pm, tc.inputSNAC)
  301. assert.NoError(t, err)
  302. assert.Equal(t, tc.expectOutput, outputSNAC)
  303. })
  304. }
  305. }
  306. func TestLocateRouter_RouteLocate(t *testing.T) {
  307. cases := []struct {
  308. // name is the unit test name
  309. name string
  310. // input is the request payload
  311. input XMessage
  312. // output is the response payload
  313. output XMessage
  314. // handlerErr is the mocked handler error response
  315. handlerErr error
  316. // expectErr is the expected error returned by the router
  317. expectErr error
  318. }{
  319. {
  320. name: "receive LocateRightsQuery, return LocateRightsReply",
  321. input: XMessage{
  322. snacFrame: oscar.SnacFrame{
  323. FoodGroup: oscar.LOCATE,
  324. SubGroup: oscar.LocateRightsQuery,
  325. },
  326. snacOut: struct{}{},
  327. },
  328. output: XMessage{
  329. snacFrame: oscar.SnacFrame{
  330. FoodGroup: oscar.LOCATE,
  331. SubGroup: oscar.LocateRightsReply,
  332. },
  333. snacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
  334. TLVRestBlock: oscar.TLVRestBlock{
  335. TLVList: oscar.TLVList{
  336. {
  337. TType: 0x01,
  338. Val: uint16(1000),
  339. },
  340. },
  341. },
  342. },
  343. },
  344. },
  345. {
  346. name: "receive LocateSetInfo, return no response",
  347. input: XMessage{
  348. snacFrame: oscar.SnacFrame{
  349. FoodGroup: oscar.LOCATE,
  350. SubGroup: oscar.LocateSetInfo,
  351. },
  352. snacOut: oscar.SNAC_0x02_0x04_LocateSetInfo{
  353. TLVRestBlock: oscar.TLVRestBlock{
  354. TLVList: oscar.TLVList{
  355. {
  356. TType: 0x01,
  357. Val: []byte{1, 2, 3, 4},
  358. },
  359. },
  360. },
  361. },
  362. },
  363. output: XMessage{
  364. snacFrame: oscar.SnacFrame{},
  365. },
  366. },
  367. {
  368. name: "receive LocateSetDirInfo, return LocateSetDirReply",
  369. input: XMessage{
  370. snacFrame: oscar.SnacFrame{
  371. FoodGroup: oscar.LOCATE,
  372. SubGroup: oscar.LocateSetDirInfo,
  373. },
  374. snacOut: oscar.SNAC_0x02_0x09_LocateSetDirInfo{
  375. TLVRestBlock: oscar.TLVRestBlock{
  376. TLVList: oscar.TLVList{
  377. {
  378. TType: 0x01,
  379. Val: []byte{1, 2, 3, 4},
  380. },
  381. },
  382. },
  383. },
  384. },
  385. output: XMessage{
  386. snacFrame: oscar.SnacFrame{
  387. FoodGroup: oscar.LOCATE,
  388. SubGroup: oscar.LocateSetDirReply,
  389. },
  390. snacOut: oscar.SNAC_0x02_0x0A_LocateSetDirReply{
  391. Result: 1,
  392. },
  393. },
  394. },
  395. {
  396. name: "receive LocateGetDirInfo, return no response",
  397. input: XMessage{
  398. snacFrame: oscar.SnacFrame{
  399. FoodGroup: oscar.LOCATE,
  400. SubGroup: oscar.LocateGetDirInfo,
  401. },
  402. snacOut: oscar.SNAC_0x02_0x0B_LocateGetDirInfo{
  403. WatcherScreenNames: "screen-name",
  404. },
  405. },
  406. output: XMessage{
  407. snacFrame: oscar.SnacFrame{},
  408. },
  409. },
  410. {
  411. name: "receive LocateSetKeywordInfo, return LocateSetKeywordReply",
  412. input: XMessage{
  413. snacFrame: oscar.SnacFrame{
  414. FoodGroup: oscar.LOCATE,
  415. SubGroup: oscar.LocateSetKeywordInfo,
  416. },
  417. snacOut: oscar.SNAC_0x02_0x0F_LocateSetKeywordInfo{
  418. TLVRestBlock: oscar.TLVRestBlock{
  419. TLVList: oscar.TLVList{
  420. {
  421. TType: 0x01,
  422. Val: []byte{1, 2, 3, 4},
  423. },
  424. },
  425. },
  426. },
  427. },
  428. output: XMessage{
  429. snacFrame: oscar.SnacFrame{
  430. FoodGroup: oscar.LOCATE,
  431. SubGroup: oscar.LocateSetKeywordReply,
  432. },
  433. snacOut: oscar.SNAC_0x02_0x10_LocateSetKeywordReply{
  434. Unknown: 1,
  435. },
  436. },
  437. },
  438. {
  439. name: "receive LocateUserInfoQuery2, return LocateUserInfoReply",
  440. input: XMessage{
  441. snacFrame: oscar.SnacFrame{
  442. FoodGroup: oscar.LOCATE,
  443. SubGroup: oscar.LocateUserInfoQuery2,
  444. },
  445. snacOut: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
  446. Type2: 1,
  447. },
  448. },
  449. output: XMessage{
  450. snacFrame: oscar.SnacFrame{
  451. FoodGroup: oscar.LOCATE,
  452. SubGroup: oscar.LocateUserInfoReply,
  453. },
  454. snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
  455. TLVUserInfo: oscar.TLVUserInfo{
  456. ScreenName: "screen-name",
  457. },
  458. LocateInfo: oscar.TLVRestBlock{
  459. TLVList: oscar.TLVList{
  460. {
  461. TType: 0x01,
  462. Val: []byte{1, 2, 3, 4},
  463. },
  464. },
  465. },
  466. },
  467. },
  468. },
  469. {
  470. name: "receive LocateGetKeywordInfo, expect ErrUnsupportedSubGroup",
  471. input: XMessage{
  472. snacFrame: oscar.SnacFrame{
  473. FoodGroup: oscar.LOCATE,
  474. SubGroup: oscar.LocateGetKeywordInfo,
  475. },
  476. snacOut: struct{}{}, // empty SNAC
  477. },
  478. output: XMessage{}, // empty SNAC
  479. expectErr: ErrUnsupportedSubGroup,
  480. },
  481. }
  482. for _, tc := range cases {
  483. t.Run(tc.name, func(t *testing.T) {
  484. svc := NewMockLocateHandler(t)
  485. svc.EXPECT().
  486. RightsQueryHandler().
  487. Return(tc.output).
  488. Maybe()
  489. svc.EXPECT().
  490. SetDirInfoHandler().
  491. Return(tc.output).
  492. Maybe()
  493. svc.EXPECT().
  494. SetInfoHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  495. Return(tc.handlerErr).
  496. Maybe()
  497. svc.EXPECT().
  498. SetKeywordInfoHandler().
  499. Return(tc.output).
  500. Maybe()
  501. svc.EXPECT().
  502. UserInfoQuery2Handler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  503. Return(tc.output, tc.handlerErr).
  504. Maybe()
  505. router := LocateRouter{
  506. LocateHandler: svc,
  507. }
  508. bufIn := &bytes.Buffer{}
  509. assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
  510. bufOut := &bytes.Buffer{}
  511. seq := uint32(1)
  512. err := router.RouteLocate(nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
  513. assert.ErrorIs(t, err, tc.expectErr)
  514. if tc.expectErr != nil {
  515. return
  516. }
  517. if tc.output.snacFrame == (oscar.SnacFrame{}) {
  518. return // handler doesn't return response
  519. }
  520. // make sure the sequence number was incremented
  521. assert.Equal(t, uint32(2), seq)
  522. // verify the FLAP frame
  523. flap := oscar.FlapFrame{}
  524. assert.NoError(t, oscar.Unmarshal(&flap, bufOut))
  525. assert.Equal(t, uint16(1), flap.Sequence)
  526. // verify the SNAC frame
  527. snacFrame := oscar.SnacFrame{}
  528. assert.NoError(t, oscar.Unmarshal(&snacFrame, bufOut))
  529. assert.Equal(t, tc.output.snacFrame, snacFrame)
  530. // verify the SNAC message
  531. snacBuf := &bytes.Buffer{}
  532. assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
  533. assert.Equal(t, snacBuf.Bytes(), bufOut.Bytes())
  534. })
  535. }
  536. }