service_test.go 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  1. package icq_legacy
  2. import (
  3. "context"
  4. "log/slog"
  5. "strconv"
  6. "testing"
  7. "time"
  8. "github.com/mk6i/open-oscar-server/config"
  9. "github.com/mk6i/open-oscar-server/state"
  10. "github.com/mk6i/open-oscar-server/wire"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/mock"
  13. )
  14. func TestICQLegacyService_AuthenticateUser(t *testing.T) {
  15. password := "secret123"
  16. authCookie := []byte("auth-cookie")
  17. screenName := state.DisplayScreenName("12345")
  18. serverCookie := state.ServerCookie{ScreenName: screenName}
  19. oscarSession := newTestOSCARInstance(screenName)
  20. successBlock := wire.TLVRestBlock{
  21. TLVList: []wire.TLV{
  22. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, authCookie),
  23. },
  24. }
  25. tests := []struct {
  26. name string
  27. setupAuth func(*mockAuthService)
  28. req AuthRequest
  29. wantResult *AuthResult
  30. wantErr error
  31. }{
  32. {
  33. name: "valid credentials",
  34. req: AuthRequest{
  35. UIN: 12345,
  36. Password: password,
  37. Version: ICQLegacyVersionV5,
  38. },
  39. setupAuth: func(authSvc *mockAuthService) {
  40. authSvc.EXPECT().FLAPLogin(mock.Anything, mock.Anything, config.Endpoint{}).
  41. Return(successBlock, nil)
  42. authSvc.EXPECT().CrackCookie(authCookie).
  43. Return(serverCookie, nil)
  44. authSvc.EXPECT().RegisterBOSSession(mock.Anything, serverCookie, mock.Anything).
  45. Return(oscarSession, nil)
  46. },
  47. wantResult: &AuthResult{
  48. Success: true,
  49. ErrorCode: 0,
  50. },
  51. },
  52. {
  53. name: "bad password",
  54. req: AuthRequest{
  55. UIN: 12345,
  56. Password: "wrongpass",
  57. Version: ICQLegacyVersionV5,
  58. },
  59. setupAuth: func(authSvc *mockAuthService) {
  60. authSvc.EXPECT().FLAPLogin(mock.Anything, mock.Anything, config.Endpoint{}).
  61. Return(wire.TLVRestBlock{
  62. TLVList: []wire.TLV{
  63. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  64. },
  65. }, nil)
  66. },
  67. wantResult: &AuthResult{
  68. Success: false,
  69. ErrorCode: 0x0001,
  70. },
  71. },
  72. {
  73. name: "user not found",
  74. req: AuthRequest{
  75. UIN: 99999,
  76. Password: password,
  77. Version: ICQLegacyVersionV5,
  78. },
  79. setupAuth: func(authSvc *mockAuthService) {
  80. authSvc.EXPECT().FLAPLogin(mock.Anything, mock.Anything, config.Endpoint{}).
  81. Return(wire.TLVRestBlock{
  82. TLVList: []wire.TLV{
  83. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr),
  84. },
  85. }, nil)
  86. },
  87. wantResult: &AuthResult{
  88. Success: false,
  89. ErrorCode: 0x0002,
  90. },
  91. },
  92. {
  93. name: "missing UIN (UIN=0)",
  94. req: AuthRequest{
  95. UIN: 0,
  96. Password: password,
  97. },
  98. wantResult: &AuthResult{
  99. Success: false,
  100. ErrorCode: 0x0002,
  101. },
  102. },
  103. {
  104. name: "no password hash",
  105. req: AuthRequest{
  106. UIN: 12345,
  107. Password: password,
  108. Version: ICQLegacyVersionV5,
  109. },
  110. setupAuth: func(authSvc *mockAuthService) {
  111. authSvc.EXPECT().FLAPLogin(mock.Anything, mock.Anything, config.Endpoint{}).
  112. Return(wire.TLVRestBlock{
  113. TLVList: []wire.TLV{
  114. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  115. },
  116. }, nil)
  117. },
  118. wantResult: &AuthResult{
  119. Success: false,
  120. ErrorCode: 0x0001,
  121. },
  122. },
  123. }
  124. for _, tc := range tests {
  125. t.Run(tc.name, func(t *testing.T) {
  126. authSvc := newMockAuthService(t)
  127. if tc.setupAuth != nil {
  128. tc.setupAuth(authSvc)
  129. }
  130. svc := NewICQLegacyService(
  131. authSvc,
  132. newMockUserManager(t),
  133. newMockAccountManager(t),
  134. newMockSessionRetriever(t),
  135. newMockMessageRelayer(t),
  136. newMockBuddyBroadcaster(t),
  137. newMockOfflineMessageManager(t),
  138. newMockICQUserFinder(t),
  139. newMockICQUserUpdater(t),
  140. newMockFeedbagManager(t),
  141. newMockRelationshipFetcher(t),
  142. newMockBuddyListRegistry(t),
  143. newMockBuddyService(t),
  144. newMockICBMService(t),
  145. &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  146. slog.Default(),
  147. )
  148. got, err := svc.AuthenticateUser(context.Background(), tc.req)
  149. if tc.wantErr != nil {
  150. assert.ErrorIs(t, err, tc.wantErr)
  151. return
  152. }
  153. assert.NoError(t, err)
  154. assert.Equal(t, tc.wantResult.Success, got.Success)
  155. assert.Equal(t, tc.wantResult.ErrorCode, got.ErrorCode)
  156. if tc.wantResult.Success {
  157. assert.NotEmpty(t, got.SessionID)
  158. assert.Same(t, oscarSession, got.oscarSession)
  159. }
  160. })
  161. }
  162. }
  163. func TestICQLegacyService_ProcessMessage(t *testing.T) {
  164. tests := []struct {
  165. name string
  166. sess *LegacySession
  167. mockParams mockParams
  168. req MessageRequest
  169. wantResult *MessageResult
  170. wantErr error
  171. legacyMgr *LegacySessionManager
  172. }{
  173. {
  174. name: "target online - legacy session exists",
  175. req: MessageRequest{
  176. FromUIN: 11111,
  177. ToUIN: 22222,
  178. MsgType: ICQLegacyMsgText,
  179. Message: "hello",
  180. },
  181. legacyMgr: &LegacySessionManager{
  182. sessions: map[uint32]*LegacySession{
  183. 22222: newTestLegacySession(22222),
  184. },
  185. },
  186. wantResult: &MessageResult{
  187. Delivered: true,
  188. StoredOffline: false,
  189. TargetOnline: true,
  190. TargetVersion: ICQLegacyVersionV5,
  191. },
  192. },
  193. {
  194. name: "target online - OSCAR session",
  195. sess: newTestLegacySession(11111, legacySessionOptOSCARSess),
  196. req: MessageRequest{
  197. FromUIN: 11111,
  198. ToUIN: 22222,
  199. MsgType: ICQLegacyMsgText,
  200. Message: "hello from legacy",
  201. },
  202. mockParams: mockParams{
  203. icbmFoodgroupParams: icbmFoodgroupParams{
  204. channelMsgToHostParams: channelMsgToHostParams{
  205. {
  206. screenName: state.NewIdentScreenName("11111"),
  207. inFrame: wire.SNACFrame{
  208. FoodGroup: wire.ICBM,
  209. SubGroup: wire.ICBMChannelMsgToHost,
  210. },
  211. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  212. ChannelID: wire.ICBMChannelICQ,
  213. ScreenName: "22222",
  214. TLVRestBlock: wire.TLVRestBlock{
  215. TLVList: wire.TLVList{
  216. wire.NewTLVLE(wire.ICBMTLVData, wire.ICBMCh4Message{
  217. UIN: 11111,
  218. MessageType: wire.ICBMMsgTypePlain,
  219. Message: "hello from legacy",
  220. }),
  221. wire.NewTLVBE(wire.ICBMTLVStore, []byte{}),
  222. },
  223. },
  224. },
  225. },
  226. },
  227. },
  228. },
  229. wantResult: &MessageResult{
  230. Delivered: true,
  231. StoredOffline: false,
  232. TargetOnline: true,
  233. TargetVersion: 0,
  234. },
  235. legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  236. },
  237. {
  238. name: "target offline - stored",
  239. sess: newTestLegacySession(11111, legacySessionOptOSCARSess),
  240. req: MessageRequest{
  241. FromUIN: 11111,
  242. ToUIN: 22222,
  243. MsgType: ICQLegacyMsgText,
  244. Message: "offline msg",
  245. },
  246. mockParams: mockParams{
  247. icbmFoodgroupParams: icbmFoodgroupParams{
  248. channelMsgToHostParams: channelMsgToHostParams{
  249. {
  250. screenName: state.NewIdentScreenName("11111"),
  251. inFrame: wire.SNACFrame{
  252. FoodGroup: wire.ICBM,
  253. SubGroup: wire.ICBMChannelMsgToHost,
  254. },
  255. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  256. ChannelID: wire.ICBMChannelICQ,
  257. ScreenName: "22222",
  258. TLVRestBlock: wire.TLVRestBlock{
  259. TLVList: wire.TLVList{
  260. wire.NewTLVLE(wire.ICBMTLVData, wire.ICBMCh4Message{
  261. UIN: 11111,
  262. MessageType: wire.ICBMMsgTypePlain,
  263. Message: "offline msg",
  264. }),
  265. wire.NewTLVBE(wire.ICBMTLVStore, []byte{}),
  266. },
  267. },
  268. },
  269. result: &wire.SNACMessage{
  270. Frame: wire.SNACFrame{
  271. FoodGroup: wire.ICBM,
  272. SubGroup: wire.ICBMErr,
  273. },
  274. Body: wire.SNACError{
  275. Code: wire.ErrorCodeNotLoggedOn,
  276. },
  277. },
  278. },
  279. },
  280. },
  281. },
  282. wantResult: &MessageResult{
  283. Delivered: false,
  284. StoredOffline: true,
  285. TargetOnline: false,
  286. TargetVersion: 0,
  287. },
  288. legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  289. },
  290. {
  291. name: "invalid sender UIN (0)",
  292. req: MessageRequest{
  293. FromUIN: 0,
  294. ToUIN: 22222,
  295. MsgType: ICQLegacyMsgText,
  296. Message: "hello",
  297. },
  298. wantResult: &MessageResult{
  299. Delivered: false,
  300. StoredOffline: false,
  301. TargetOnline: false,
  302. },
  303. legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  304. },
  305. {
  306. name: "invalid target UIN (0)",
  307. req: MessageRequest{
  308. FromUIN: 11111,
  309. ToUIN: 0,
  310. MsgType: ICQLegacyMsgText,
  311. Message: "hello",
  312. },
  313. wantResult: &MessageResult{
  314. Delivered: false,
  315. StoredOffline: false,
  316. TargetOnline: false,
  317. },
  318. legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  319. },
  320. }
  321. for _, tc := range tests {
  322. t.Run(tc.name, func(t *testing.T) {
  323. icbmSvc := newMockICBMService(t)
  324. for _, msg := range tc.mockParams.channelMsgToHostParams {
  325. icbmSvc.EXPECT().
  326. ChannelMsgToHost(matchContext(), matchSession(msg.screenName), msg.inFrame, msg.inBody).
  327. Return(msg.result, msg.err)
  328. }
  329. svc := NewICQLegacyService(
  330. newMockAuthService(t),
  331. newMockUserManager(t),
  332. newMockAccountManager(t),
  333. newMockSessionRetriever(t),
  334. newMockMessageRelayer(t),
  335. newMockBuddyBroadcaster(t),
  336. newMockOfflineMessageManager(t),
  337. newMockICQUserFinder(t),
  338. newMockICQUserUpdater(t),
  339. newMockFeedbagManager(t),
  340. newMockRelationshipFetcher(t),
  341. newMockBuddyListRegistry(t),
  342. newMockBuddyService(t),
  343. icbmSvc,
  344. tc.legacyMgr,
  345. slog.Default(),
  346. )
  347. got, err := svc.ProcessMessage(context.Background(), tc.sess, tc.req)
  348. if tc.wantErr != nil {
  349. assert.ErrorIs(t, err, tc.wantErr)
  350. return
  351. }
  352. assert.NoError(t, err)
  353. assert.Equal(t, tc.wantResult.Delivered, got.Delivered)
  354. assert.Equal(t, tc.wantResult.StoredOffline, got.StoredOffline)
  355. assert.Equal(t, tc.wantResult.TargetOnline, got.TargetOnline)
  356. assert.Equal(t, tc.wantResult.TargetVersion, got.TargetVersion)
  357. })
  358. }
  359. }
  360. func TestICQLegacyService_ProcessContactList(t *testing.T) {
  361. tests := []struct {
  362. name string
  363. mockParams mockParams
  364. req ContactListRequest
  365. legacyMgr *LegacySessionManager
  366. wantResult *ContactListResult
  367. }{
  368. {
  369. name: "mixed online/offline contacts",
  370. req: ContactListRequest{
  371. UIN: 11111,
  372. Contacts: []uint32{22222, 33333},
  373. },
  374. mockParams: mockParams{
  375. sessionRetrieverParams: sessionRetrieverParams{
  376. retrieveSessionParams: retrieveSessionParams{
  377. {
  378. screenName: state.NewIdentScreenName("22222"),
  379. result: nil, // not on OSCAR either
  380. },
  381. {
  382. screenName: state.NewIdentScreenName("33333"),
  383. result: &state.Session{}, // online via OSCAR
  384. },
  385. },
  386. },
  387. },
  388. legacyMgr: &LegacySessionManager{
  389. sessions: make(map[uint32]*LegacySession),
  390. },
  391. wantResult: &ContactListResult{
  392. OnlineContacts: []ContactStatus{
  393. {UIN: 22222, Online: false, Status: 0},
  394. {UIN: 33333, Online: true, Status: ICQLegacyStatusOnline},
  395. },
  396. },
  397. },
  398. {
  399. name: "empty list",
  400. req: ContactListRequest{
  401. UIN: 11111,
  402. Contacts: []uint32{},
  403. },
  404. legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  405. wantResult: &ContactListResult{
  406. OnlineContacts: []ContactStatus{},
  407. },
  408. },
  409. }
  410. for _, tc := range tests {
  411. t.Run(tc.name, func(t *testing.T) {
  412. sessionRetriever := newMockSessionRetriever(t)
  413. for _, p := range tc.mockParams.retrieveSessionParams {
  414. sessionRetriever.EXPECT().
  415. RetrieveSession(p.screenName).
  416. Return(p.result)
  417. }
  418. buddySvc := newMockBuddyService(t)
  419. userFinder := newMockICQUserFinder(t)
  420. // ProcessContactList adds both forward and reverse buddy entries
  421. buddySvc.EXPECT().
  422. AddBuddies(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
  423. Return(nil, nil).
  424. Maybe()
  425. userFinder.EXPECT().
  426. FindByUIN(mock.Anything, mock.Anything).
  427. Return(state.User{}, nil).
  428. Maybe()
  429. svc := NewICQLegacyService(
  430. newMockAuthService(t),
  431. newMockUserManager(t),
  432. newMockAccountManager(t),
  433. sessionRetriever,
  434. newMockMessageRelayer(t),
  435. newMockBuddyBroadcaster(t),
  436. newMockOfflineMessageManager(t),
  437. userFinder,
  438. newMockICQUserUpdater(t),
  439. newMockFeedbagManager(t),
  440. newMockRelationshipFetcher(t),
  441. newMockBuddyListRegistry(t),
  442. buddySvc,
  443. newMockICBMService(t),
  444. tc.legacyMgr,
  445. slog.Default(),
  446. )
  447. var instance *state.SessionInstance
  448. if tc.req.UIN != 0 {
  449. instance = newTestOSCARInstance(state.DisplayScreenName(strconv.FormatUint(uint64(tc.req.UIN), 10)))
  450. }
  451. got, err := svc.ProcessContactList(context.Background(), instance, tc.req)
  452. assert.NoError(t, err)
  453. assert.Equal(t, len(tc.wantResult.OnlineContacts), len(got.OnlineContacts))
  454. for i, want := range tc.wantResult.OnlineContacts {
  455. assert.Equal(t, want.UIN, got.OnlineContacts[i].UIN)
  456. assert.Equal(t, want.Online, got.OnlineContacts[i].Online)
  457. if want.Online {
  458. assert.Equal(t, want.Status, got.OnlineContacts[i].Status)
  459. }
  460. }
  461. })
  462. }
  463. }
  464. func TestICQLegacyService_ProcessStatusChange(t *testing.T) {
  465. tests := []struct {
  466. name string
  467. mockParams mockParams
  468. req StatusChangeRequest
  469. legacyMgr *LegacySessionManager
  470. wantTargets int
  471. }{
  472. {
  473. name: "status change with notification targets",
  474. req: StatusChangeRequest{
  475. UIN: 11111,
  476. OldStatus: ICQLegacyStatusOnline,
  477. NewStatus: ICQLegacyStatusAway,
  478. },
  479. legacyMgr: &LegacySessionManager{
  480. sessions: map[uint32]*LegacySession{
  481. 11111: newTestLegacySession(11111, legacySessionOptContactList([]uint32{22222})),
  482. 22222: newTestLegacySession(22222, legacySessionOptContactList([]uint32{11111})),
  483. },
  484. },
  485. wantTargets: 1,
  486. },
  487. {
  488. name: "no targets - session not found",
  489. req: StatusChangeRequest{
  490. UIN: 99999,
  491. OldStatus: ICQLegacyStatusOnline,
  492. NewStatus: ICQLegacyStatusAway,
  493. },
  494. legacyMgr: &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  495. wantTargets: 0,
  496. },
  497. }
  498. for _, tc := range tests {
  499. t.Run(tc.name, func(t *testing.T) {
  500. svc := NewICQLegacyService(
  501. newMockAuthService(t),
  502. newMockUserManager(t),
  503. newMockAccountManager(t),
  504. newMockSessionRetriever(t),
  505. newMockMessageRelayer(t),
  506. newMockBuddyBroadcaster(t),
  507. newMockOfflineMessageManager(t),
  508. newMockICQUserFinder(t),
  509. newMockICQUserUpdater(t),
  510. newMockFeedbagManager(t),
  511. newMockRelationshipFetcher(t),
  512. newMockBuddyListRegistry(t),
  513. newMockBuddyService(t),
  514. newMockICBMService(t),
  515. tc.legacyMgr,
  516. slog.Default(),
  517. )
  518. got, err := svc.ProcessStatusChange(context.Background(), tc.req)
  519. assert.NoError(t, err)
  520. assert.Len(t, got.NotifyTargets, tc.wantTargets)
  521. })
  522. }
  523. }
  524. func TestICQLegacyService_SearchByUIN(t *testing.T) {
  525. tests := []struct {
  526. name string
  527. mockParams mockParams
  528. uin uint32
  529. wantResult *LegacyUserSearchResult
  530. wantErr bool
  531. }{
  532. {
  533. name: "found",
  534. uin: 12345,
  535. mockParams: mockParams{
  536. icqUserFinderParams: icqUserFinderParams{
  537. findByUINParams: findByUINParams{
  538. {
  539. UIN: 12345,
  540. result: state.User{
  541. IdentScreenName: state.NewIdentScreenName("12345"),
  542. ICQInfo: state.ICQInfo{
  543. Basic: state.ICQBasicInfo{
  544. Nickname: "CoolUser",
  545. FirstName: "John",
  546. LastName: "Doe",
  547. EmailAddress: "john@example.com",
  548. },
  549. },
  550. },
  551. },
  552. },
  553. },
  554. sessionRetrieverParams: sessionRetrieverParams{
  555. retrieveSessionParams: retrieveSessionParams{
  556. {
  557. screenName: state.NewIdentScreenName("12345"),
  558. result: nil,
  559. },
  560. },
  561. },
  562. },
  563. wantResult: &LegacyUserSearchResult{
  564. UIN: 12345,
  565. Nickname: "CoolUser",
  566. FirstName: "John",
  567. LastName: "Doe",
  568. Email: "john@example.com",
  569. },
  570. },
  571. {
  572. name: "not found",
  573. uin: 99999,
  574. mockParams: mockParams{
  575. icqUserFinderParams: icqUserFinderParams{
  576. findByUINParams: findByUINParams{
  577. {
  578. UIN: 99999,
  579. err: state.ErrNoUser,
  580. },
  581. },
  582. },
  583. },
  584. wantErr: true,
  585. },
  586. }
  587. for _, tc := range tests {
  588. t.Run(tc.name, func(t *testing.T) {
  589. userFinder := newMockICQUserFinder(t)
  590. for _, p := range tc.mockParams.findByUINParams {
  591. userFinder.EXPECT().
  592. FindByUIN(matchContext(), p.UIN).
  593. Return(p.result, p.err)
  594. }
  595. sessionRetriever := newMockSessionRetriever(t)
  596. for _, p := range tc.mockParams.retrieveSessionParams {
  597. sessionRetriever.EXPECT().
  598. RetrieveSession(p.screenName).
  599. Return(p.result)
  600. }
  601. svc := NewICQLegacyService(
  602. newMockAuthService(t),
  603. newMockUserManager(t),
  604. newMockAccountManager(t),
  605. sessionRetriever,
  606. newMockMessageRelayer(t),
  607. newMockBuddyBroadcaster(t),
  608. newMockOfflineMessageManager(t),
  609. userFinder,
  610. newMockICQUserUpdater(t),
  611. newMockFeedbagManager(t),
  612. newMockRelationshipFetcher(t),
  613. newMockBuddyListRegistry(t),
  614. newMockBuddyService(t),
  615. newMockICBMService(t),
  616. &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  617. slog.Default(),
  618. )
  619. got, err := svc.SearchByUIN(context.Background(), tc.uin)
  620. if tc.wantErr {
  621. assert.Error(t, err)
  622. return
  623. }
  624. assert.NoError(t, err)
  625. assert.Equal(t, tc.wantResult.UIN, got.UIN)
  626. assert.Equal(t, tc.wantResult.Nickname, got.Nickname)
  627. assert.Equal(t, tc.wantResult.FirstName, got.FirstName)
  628. assert.Equal(t, tc.wantResult.LastName, got.LastName)
  629. assert.Equal(t, tc.wantResult.Email, got.Email)
  630. })
  631. }
  632. }
  633. func TestICQLegacyService_SearchByName(t *testing.T) {
  634. tests := []struct {
  635. name string
  636. mockParams mockParams
  637. nick string
  638. first string
  639. last string
  640. email string
  641. wantCount int
  642. }{
  643. {
  644. name: "matches by name",
  645. first: "John",
  646. last: "Doe",
  647. mockParams: mockParams{
  648. icqUserFinderParams: icqUserFinderParams{
  649. findByICQNameParams: findByICQNameParams{
  650. {
  651. firstName: "John",
  652. lastName: "Doe",
  653. nickName: "",
  654. result: []state.User{
  655. {
  656. IdentScreenName: state.NewIdentScreenName("12345"),
  657. ICQInfo: state.ICQInfo{
  658. Basic: state.ICQBasicInfo{
  659. Nickname: "JD",
  660. FirstName: "John",
  661. LastName: "Doe",
  662. },
  663. },
  664. },
  665. },
  666. },
  667. },
  668. },
  669. sessionRetrieverParams: sessionRetrieverParams{
  670. retrieveSessionParams: retrieveSessionParams{
  671. {
  672. screenName: state.NewIdentScreenName("12345"),
  673. result: nil,
  674. },
  675. },
  676. },
  677. },
  678. wantCount: 1,
  679. },
  680. {
  681. name: "matches by email",
  682. email: "john@example.com",
  683. mockParams: mockParams{
  684. icqUserFinderParams: icqUserFinderParams{
  685. findByICQEmailParams: findByICQEmailParams{
  686. {
  687. email: "john@example.com",
  688. result: state.User{
  689. IdentScreenName: state.NewIdentScreenName("12345"),
  690. ICQInfo: state.ICQInfo{
  691. Basic: state.ICQBasicInfo{
  692. Nickname: "JD",
  693. EmailAddress: "john@example.com",
  694. },
  695. },
  696. },
  697. },
  698. },
  699. },
  700. sessionRetrieverParams: sessionRetrieverParams{
  701. retrieveSessionParams: retrieveSessionParams{
  702. {
  703. screenName: state.NewIdentScreenName("12345"),
  704. result: nil,
  705. },
  706. },
  707. },
  708. },
  709. wantCount: 1,
  710. },
  711. }
  712. for _, tc := range tests {
  713. t.Run(tc.name, func(t *testing.T) {
  714. userFinder := newMockICQUserFinder(t)
  715. for _, p := range tc.mockParams.findByICQNameParams {
  716. userFinder.EXPECT().
  717. FindByICQName(matchContext(), p.firstName, p.lastName, p.nickName).
  718. Return(p.result, p.err)
  719. }
  720. for _, p := range tc.mockParams.findByICQEmailParams {
  721. userFinder.EXPECT().
  722. FindByICQEmail(matchContext(), p.email).
  723. Return(p.result, p.err)
  724. }
  725. sessionRetriever := newMockSessionRetriever(t)
  726. for _, p := range tc.mockParams.retrieveSessionParams {
  727. sessionRetriever.EXPECT().
  728. RetrieveSession(p.screenName).
  729. Return(p.result)
  730. }
  731. svc := NewICQLegacyService(
  732. newMockAuthService(t),
  733. newMockUserManager(t),
  734. newMockAccountManager(t),
  735. sessionRetriever,
  736. newMockMessageRelayer(t),
  737. newMockBuddyBroadcaster(t),
  738. newMockOfflineMessageManager(t),
  739. userFinder,
  740. newMockICQUserUpdater(t),
  741. newMockFeedbagManager(t),
  742. newMockRelationshipFetcher(t),
  743. newMockBuddyListRegistry(t),
  744. newMockBuddyService(t),
  745. newMockICBMService(t),
  746. &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  747. slog.Default(),
  748. )
  749. got, err := svc.SearchByName(context.Background(), tc.nick, tc.first, tc.last, tc.email)
  750. assert.NoError(t, err)
  751. assert.Len(t, got, tc.wantCount)
  752. })
  753. }
  754. }
  755. func TestICQLegacyService_GetOfflineMessages(t *testing.T) {
  756. fixedTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
  757. tests := []struct {
  758. name string
  759. mockParams mockParams
  760. uin uint32
  761. wantCount int
  762. }{
  763. {
  764. name: "messages exist",
  765. uin: 12345,
  766. mockParams: mockParams{
  767. offlineMessageManagerParams: offlineMessageManagerParams{
  768. retrieveMessagesParams: retrieveMessagesParams{
  769. {
  770. recip: state.NewIdentScreenName("12345"),
  771. messages: []state.OfflineMessage{
  772. {
  773. Sender: state.NewIdentScreenName("99999"),
  774. Recipient: state.NewIdentScreenName("12345"),
  775. Sent: fixedTime,
  776. Message: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  777. ChannelID: wire.ICBMChannelIM,
  778. TLVRestBlock: wire.TLVRestBlock{
  779. TLVList: func() wire.TLVList {
  780. frags, _ := wire.ICBMFragmentList("hello offline")
  781. return wire.TLVList{
  782. wire.NewTLVBE(wire.ICBMTLVAOLIMData, frags),
  783. }
  784. }(),
  785. },
  786. },
  787. },
  788. },
  789. },
  790. },
  791. },
  792. },
  793. wantCount: 1,
  794. },
  795. {
  796. name: "no messages",
  797. uin: 12345,
  798. mockParams: mockParams{
  799. offlineMessageManagerParams: offlineMessageManagerParams{
  800. retrieveMessagesParams: retrieveMessagesParams{
  801. {
  802. recip: state.NewIdentScreenName("12345"),
  803. messages: []state.OfflineMessage{},
  804. },
  805. },
  806. },
  807. },
  808. wantCount: 0,
  809. },
  810. }
  811. for _, tc := range tests {
  812. t.Run(tc.name, func(t *testing.T) {
  813. offlineMsgMgr := newMockOfflineMessageManager(t)
  814. for _, p := range tc.mockParams.retrieveMessagesParams {
  815. offlineMsgMgr.EXPECT().
  816. RetrieveMessages(matchContext(), p.recip).
  817. Return(p.messages, p.err)
  818. }
  819. svc := NewICQLegacyService(
  820. newMockAuthService(t),
  821. newMockUserManager(t),
  822. newMockAccountManager(t),
  823. newMockSessionRetriever(t),
  824. newMockMessageRelayer(t),
  825. newMockBuddyBroadcaster(t),
  826. offlineMsgMgr,
  827. newMockICQUserFinder(t),
  828. newMockICQUserUpdater(t),
  829. newMockFeedbagManager(t),
  830. newMockRelationshipFetcher(t),
  831. newMockBuddyListRegistry(t),
  832. newMockBuddyService(t),
  833. newMockICBMService(t),
  834. &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  835. slog.Default(),
  836. )
  837. got, err := svc.GetOfflineMessages(context.Background(), tc.uin)
  838. assert.NoError(t, err)
  839. assert.Len(t, got, tc.wantCount)
  840. if tc.wantCount > 0 {
  841. assert.Equal(t, uint32(99999), got[0].FromUIN)
  842. assert.Equal(t, tc.uin, got[0].ToUIN)
  843. }
  844. })
  845. }
  846. }
  847. func TestICQLegacyService_RegisterNewUser(t *testing.T) {
  848. tests := []struct {
  849. name string
  850. wantErr bool
  851. }{
  852. {
  853. name: "success",
  854. },
  855. }
  856. for _, tc := range tests {
  857. t.Run(tc.name, func(t *testing.T) {
  858. userManager := newMockUserManager(t)
  859. // generateNewUIN calls User() to find an available UIN.
  860. // Return nil (available) for the first UIN checked (100000).
  861. userManager.EXPECT().
  862. User(matchContext(), state.NewIdentScreenName(strconv.FormatUint(100000, 10))).
  863. Return(nil, nil)
  864. // InsertUser is called with the new user
  865. userManager.EXPECT().
  866. InsertUser(matchContext(), mock.Anything).
  867. Return(nil)
  868. svc := NewICQLegacyService(
  869. newMockAuthService(t),
  870. userManager,
  871. newMockAccountManager(t),
  872. newMockSessionRetriever(t),
  873. newMockMessageRelayer(t),
  874. newMockBuddyBroadcaster(t),
  875. newMockOfflineMessageManager(t),
  876. newMockICQUserFinder(t),
  877. newMockICQUserUpdater(t),
  878. newMockFeedbagManager(t),
  879. newMockRelationshipFetcher(t),
  880. newMockBuddyListRegistry(t),
  881. newMockBuddyService(t),
  882. newMockICBMService(t),
  883. &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  884. slog.Default(),
  885. )
  886. uin, err := svc.RegisterNewUser(context.Background(), "Nick", "First", "Last", "test@example.com", "password123")
  887. if tc.wantErr {
  888. assert.Error(t, err)
  889. return
  890. }
  891. assert.NoError(t, err)
  892. assert.Equal(t, uint32(100000), uin)
  893. })
  894. }
  895. }
  896. func TestICQLegacyService_DeleteUser(t *testing.T) {
  897. authKey := "test-auth-key"
  898. password := "secret123"
  899. passHash := wire.StrongMD5PasswordHash(password, authKey)
  900. tests := []struct {
  901. name string
  902. mockParams mockParams
  903. uin uint32
  904. password string
  905. wantErr bool
  906. }{
  907. {
  908. name: "success",
  909. uin: 12345,
  910. password: password,
  911. mockParams: mockParams{
  912. userManagerParams: userManagerParams{
  913. userParams: userParams{
  914. {
  915. screenName: state.NewIdentScreenName("12345"),
  916. result: &state.User{
  917. AuthKey: authKey,
  918. StrongMD5Pass: passHash,
  919. },
  920. },
  921. },
  922. deleteUserParams: deleteUserParams{
  923. {
  924. screenName: state.NewIdentScreenName("12345"),
  925. },
  926. },
  927. },
  928. },
  929. },
  930. {
  931. name: "wrong password",
  932. uin: 12345,
  933. password: "wrongpass",
  934. mockParams: mockParams{
  935. userManagerParams: userManagerParams{
  936. userParams: userParams{
  937. {
  938. screenName: state.NewIdentScreenName("12345"),
  939. result: &state.User{
  940. AuthKey: authKey,
  941. StrongMD5Pass: passHash,
  942. },
  943. },
  944. },
  945. },
  946. },
  947. wantErr: true,
  948. },
  949. }
  950. for _, tc := range tests {
  951. t.Run(tc.name, func(t *testing.T) {
  952. authSvc := newMockAuthService(t)
  953. if tc.wantErr {
  954. authSvc.EXPECT().FLAPLogin(mock.Anything, mock.Anything, config.Endpoint{}).
  955. Return(wire.TLVRestBlock{
  956. TLVList: []wire.TLV{
  957. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  958. },
  959. }, nil)
  960. } else {
  961. authSvc.EXPECT().FLAPLogin(mock.Anything, mock.Anything, config.Endpoint{}).
  962. Return(wire.TLVRestBlock{}, nil)
  963. }
  964. userManager := newMockUserManager(t)
  965. for _, p := range tc.mockParams.deleteUserParams {
  966. userManager.EXPECT().
  967. DeleteUser(matchContext(), p.screenName).
  968. Return(p.err)
  969. }
  970. svc := NewICQLegacyService(
  971. authSvc,
  972. userManager,
  973. newMockAccountManager(t),
  974. newMockSessionRetriever(t),
  975. newMockMessageRelayer(t),
  976. newMockBuddyBroadcaster(t),
  977. newMockOfflineMessageManager(t),
  978. newMockICQUserFinder(t),
  979. newMockICQUserUpdater(t),
  980. newMockFeedbagManager(t),
  981. newMockRelationshipFetcher(t),
  982. newMockBuddyListRegistry(t),
  983. newMockBuddyService(t),
  984. newMockICBMService(t),
  985. &LegacySessionManager{sessions: map[uint32]*LegacySession{}},
  986. slog.Default(),
  987. )
  988. err := svc.DeleteUser(context.Background(), tc.uin, tc.password)
  989. if tc.wantErr {
  990. assert.Error(t, err)
  991. assert.Contains(t, err.Error(), "invalid password")
  992. return
  993. }
  994. assert.NoError(t, err)
  995. })
  996. }
  997. }