service_test.go 26 KB

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