auth_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. package handler
  2. import (
  3. "io"
  4. "testing"
  5. "github.com/google/uuid"
  6. "github.com/mk6i/retro-aim-server/config"
  7. "github.com/mk6i/retro-aim-server/oscar"
  8. "github.com/mk6i/retro-aim-server/state"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/mock"
  11. )
  12. func TestAuthService_BUCPLoginRequestHandler(t *testing.T) {
  13. sessUUID := uuid.UUID{1, 2, 3}
  14. user := state.User{
  15. ScreenName: "screen_name",
  16. AuthKey: "auth_key",
  17. }
  18. assert.NoError(t, user.HashPassword("the_password"))
  19. userSession := newTestSession(user.ScreenName, sessOptID(sessUUID.String()))
  20. cases := []struct {
  21. // name is the unit test name
  22. name string
  23. // cfg is the app configuration
  24. cfg config.Config
  25. // inputSNAC is the SNAC sent from the client to the server
  26. inputSNAC oscar.SNAC_0x17_0x02_BUCPLoginRequest
  27. // mockParams is the list of params sent to mocks that satisfy this
  28. // method's dependencies
  29. mockParams mockParams
  30. // newUserFn is the function that registers a new user account
  31. newUserFn func(screenName string) (state.User, error)
  32. // expectOutput is the SNAC sent from the server to client
  33. expectOutput oscar.SNACMessage
  34. // wantErr is the error we expect from the method
  35. wantErr error
  36. }{
  37. {
  38. name: "user provides valid credentials and logs in successfully",
  39. cfg: config.Config{
  40. OSCARHost: "127.0.0.1",
  41. BOSPort: 1234,
  42. },
  43. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  44. TLVRestBlock: oscar.TLVRestBlock{
  45. TLVList: oscar.TLVList{
  46. oscar.NewTLV(oscar.TLVPasswordHash, user.PassHash),
  47. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  48. },
  49. },
  50. },
  51. mockParams: mockParams{
  52. userManagerParams: userManagerParams{
  53. getUserParams: getUserParams{
  54. {
  55. screenName: user.ScreenName,
  56. result: &user,
  57. },
  58. },
  59. },
  60. sessionManagerParams: sessionManagerParams{
  61. addSessionParams: addSessionParams{
  62. {
  63. sessID: userSession.ID(),
  64. screenName: user.ScreenName,
  65. result: userSession,
  66. },
  67. },
  68. },
  69. },
  70. expectOutput: oscar.SNACMessage{
  71. Frame: oscar.SNACFrame{
  72. FoodGroup: oscar.BUCP,
  73. SubGroup: oscar.BUCPLoginResponse,
  74. },
  75. Body: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
  76. TLVRestBlock: oscar.TLVRestBlock{
  77. TLVList: oscar.TLVList{
  78. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  79. oscar.NewTLV(oscar.TLVReconnectHere, "127.0.0.1:1234"),
  80. oscar.NewTLV(oscar.TLVAuthorizationCookie, sessUUID.String()),
  81. },
  82. },
  83. },
  84. },
  85. },
  86. {
  87. name: "user logs in with non-existent screen name--account is created and logged in successfully",
  88. cfg: config.Config{
  89. OSCARHost: "127.0.0.1",
  90. BOSPort: 1234,
  91. DisableAuth: true,
  92. },
  93. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  94. TLVRestBlock: oscar.TLVRestBlock{
  95. TLVList: oscar.TLVList{
  96. oscar.NewTLV(oscar.TLVPasswordHash, user.PassHash),
  97. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  98. },
  99. },
  100. },
  101. mockParams: mockParams{
  102. userManagerParams: userManagerParams{
  103. getUserParams: getUserParams{
  104. {
  105. screenName: user.ScreenName,
  106. result: nil,
  107. },
  108. },
  109. upsertUserParams: upsertUserParams{
  110. {
  111. user: user,
  112. },
  113. },
  114. },
  115. sessionManagerParams: sessionManagerParams{
  116. addSessionParams: addSessionParams{
  117. {
  118. sessID: userSession.ID(),
  119. screenName: user.ScreenName,
  120. result: userSession,
  121. },
  122. },
  123. },
  124. },
  125. newUserFn: func(screenName string) (state.User, error) {
  126. return user, nil
  127. },
  128. expectOutput: oscar.SNACMessage{
  129. Frame: oscar.SNACFrame{
  130. FoodGroup: oscar.BUCP,
  131. SubGroup: oscar.BUCPLoginResponse,
  132. },
  133. Body: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
  134. TLVRestBlock: oscar.TLVRestBlock{
  135. TLVList: oscar.TLVList{
  136. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  137. oscar.NewTLV(oscar.TLVReconnectHere, "127.0.0.1:1234"),
  138. oscar.NewTLV(oscar.TLVAuthorizationCookie, sessUUID.String()),
  139. },
  140. },
  141. },
  142. },
  143. },
  144. {
  145. name: "user logs in with invalid password--account is created and logged in successfully",
  146. cfg: config.Config{
  147. OSCARHost: "127.0.0.1",
  148. BOSPort: 1234,
  149. DisableAuth: true,
  150. },
  151. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  152. TLVRestBlock: oscar.TLVRestBlock{
  153. TLVList: oscar.TLVList{
  154. oscar.NewTLV(oscar.TLVPasswordHash, []byte("bad-password-hash")),
  155. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  156. },
  157. },
  158. },
  159. mockParams: mockParams{
  160. userManagerParams: userManagerParams{
  161. getUserParams: getUserParams{
  162. {
  163. screenName: user.ScreenName,
  164. result: &user,
  165. },
  166. },
  167. upsertUserParams: upsertUserParams{
  168. {
  169. user: user,
  170. },
  171. },
  172. },
  173. sessionManagerParams: sessionManagerParams{
  174. addSessionParams: addSessionParams{
  175. {
  176. sessID: userSession.ID(),
  177. screenName: user.ScreenName,
  178. result: userSession,
  179. },
  180. },
  181. },
  182. },
  183. newUserFn: func(screenName string) (state.User, error) {
  184. return user, nil
  185. },
  186. expectOutput: oscar.SNACMessage{
  187. Frame: oscar.SNACFrame{
  188. FoodGroup: oscar.BUCP,
  189. SubGroup: oscar.BUCPLoginResponse,
  190. },
  191. Body: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
  192. TLVRestBlock: oscar.TLVRestBlock{
  193. TLVList: oscar.TLVList{
  194. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  195. oscar.NewTLV(oscar.TLVReconnectHere, "127.0.0.1:1234"),
  196. oscar.NewTLV(oscar.TLVAuthorizationCookie, sessUUID.String()),
  197. },
  198. },
  199. },
  200. },
  201. },
  202. {
  203. name: "user provides invalid password--account creation fails due to user creation runtime error",
  204. cfg: config.Config{
  205. DisableAuth: true,
  206. },
  207. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  208. TLVRestBlock: oscar.TLVRestBlock{
  209. TLVList: oscar.TLVList{
  210. oscar.NewTLV(oscar.TLVPasswordHash, []byte("bad-password-hash")),
  211. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  212. },
  213. },
  214. },
  215. mockParams: mockParams{
  216. userManagerParams: userManagerParams{
  217. getUserParams: getUserParams{
  218. {
  219. screenName: user.ScreenName,
  220. result: &user,
  221. },
  222. },
  223. },
  224. },
  225. newUserFn: func(screenName string) (state.User, error) {
  226. return user, io.EOF
  227. },
  228. wantErr: io.EOF,
  229. },
  230. {
  231. name: "user provides invalid password--account creation fails due to user upsert runtime error",
  232. cfg: config.Config{
  233. DisableAuth: true,
  234. },
  235. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  236. TLVRestBlock: oscar.TLVRestBlock{
  237. TLVList: oscar.TLVList{
  238. oscar.NewTLV(oscar.TLVPasswordHash, []byte("bad-password-hash")),
  239. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  240. },
  241. },
  242. },
  243. mockParams: mockParams{
  244. userManagerParams: userManagerParams{
  245. getUserParams: getUserParams{
  246. {
  247. screenName: user.ScreenName,
  248. result: &user,
  249. },
  250. },
  251. upsertUserParams: upsertUserParams{
  252. {
  253. user: user,
  254. err: io.EOF,
  255. },
  256. },
  257. },
  258. },
  259. newUserFn: func(screenName string) (state.User, error) {
  260. return user, nil
  261. },
  262. wantErr: io.EOF,
  263. },
  264. {
  265. name: "user provides invalid password and receives invalid login response",
  266. cfg: config.Config{
  267. OSCARHost: "127.0.0.1",
  268. BOSPort: 1234,
  269. },
  270. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  271. TLVRestBlock: oscar.TLVRestBlock{
  272. TLVList: oscar.TLVList{
  273. oscar.NewTLV(oscar.TLVPasswordHash, []byte("bad_password")),
  274. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  275. },
  276. },
  277. },
  278. mockParams: mockParams{
  279. userManagerParams: userManagerParams{
  280. getUserParams: getUserParams{
  281. {
  282. screenName: user.ScreenName,
  283. result: nil,
  284. },
  285. },
  286. },
  287. },
  288. expectOutput: oscar.SNACMessage{
  289. Frame: oscar.SNACFrame{
  290. FoodGroup: oscar.BUCP,
  291. SubGroup: oscar.BUCPLoginResponse,
  292. },
  293. Body: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
  294. TLVRestBlock: oscar.TLVRestBlock{
  295. TLVList: oscar.TLVList{
  296. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  297. oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)),
  298. },
  299. },
  300. },
  301. },
  302. },
  303. {
  304. name: "login fails on user manager lookup",
  305. inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
  306. TLVRestBlock: oscar.TLVRestBlock{
  307. TLVList: oscar.TLVList{
  308. oscar.NewTLV(oscar.TLVPasswordHash, user.PassHash),
  309. oscar.NewTLV(oscar.TLVScreenName, user.ScreenName),
  310. },
  311. },
  312. },
  313. mockParams: mockParams{
  314. userManagerParams: userManagerParams{
  315. getUserParams: getUserParams{
  316. {
  317. screenName: user.ScreenName,
  318. err: io.EOF,
  319. },
  320. },
  321. },
  322. },
  323. wantErr: io.EOF,
  324. },
  325. }
  326. for _, tc := range cases {
  327. t.Run(tc.name, func(t *testing.T) {
  328. userManager := newMockUserManager(t)
  329. for _, params := range tc.mockParams.getUserParams {
  330. userManager.EXPECT().
  331. User(params.screenName).
  332. Return(params.result, params.err)
  333. }
  334. for _, params := range tc.mockParams.upsertUserParams {
  335. userManager.EXPECT().
  336. InsertUser(params.user).
  337. Return(params.err)
  338. }
  339. sessionManager := newMockSessionManager(t)
  340. for _, params := range tc.mockParams.addSessionParams {
  341. sessionManager.EXPECT().
  342. AddSession(params.sessID, params.screenName).
  343. Return(params.result)
  344. }
  345. svc := AuthService{
  346. config: tc.cfg,
  347. sessionManager: sessionManager,
  348. userManager: userManager,
  349. }
  350. fnNewUUID := func() uuid.UUID {
  351. return sessUUID
  352. }
  353. outputSNAC, err := svc.BUCPLoginRequestHandler(tc.inputSNAC, fnNewUUID, tc.newUserFn)
  354. assert.ErrorIs(t, err, tc.wantErr)
  355. assert.Equal(t, tc.expectOutput, outputSNAC)
  356. })
  357. }
  358. }
  359. func TestAuthService_BUCPChallengeRequestHandler(t *testing.T) {
  360. sessUUID := uuid.UUID{1, 2, 3}
  361. cases := []struct {
  362. // name is the unit test name
  363. name string
  364. // cfg is the app configuration
  365. cfg config.Config
  366. // inputSNAC is the SNAC sent from the client to the server
  367. inputSNAC oscar.SNAC_0x17_0x06_BUCPChallengeRequest
  368. // mockParams is the list of params sent to mocks that satisfy this
  369. // method's dependencies
  370. mockParams mockParams
  371. // expectOutput is the SNAC sent from the server to client
  372. expectOutput oscar.SNACMessage
  373. // wantErr is the error we expect from the method
  374. wantErr error
  375. }{
  376. {
  377. name: "login with valid username, expect OK login response",
  378. cfg: config.Config{
  379. OSCARHost: "127.0.0.1",
  380. BOSPort: 1234,
  381. },
  382. inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
  383. TLVRestBlock: oscar.TLVRestBlock{
  384. TLVList: oscar.TLVList{
  385. oscar.NewTLV(oscar.TLVScreenName, "sn_user_a"),
  386. },
  387. },
  388. },
  389. mockParams: mockParams{
  390. userManagerParams: userManagerParams{
  391. getUserParams: getUserParams{
  392. {
  393. screenName: "sn_user_a",
  394. result: &state.User{
  395. ScreenName: "sn_user_a",
  396. AuthKey: "auth_key_user_a",
  397. },
  398. },
  399. },
  400. },
  401. },
  402. expectOutput: oscar.SNACMessage{
  403. Frame: oscar.SNACFrame{
  404. FoodGroup: oscar.BUCP,
  405. SubGroup: oscar.BUCPChallengeResponse,
  406. },
  407. Body: oscar.SNAC_0x17_0x07_BUCPChallengeResponse{
  408. AuthKey: "auth_key_user_a",
  409. },
  410. },
  411. },
  412. {
  413. name: "login with invalid username, expect OK login response (Cfg.DisableAuth=true)",
  414. cfg: config.Config{
  415. OSCARHost: "127.0.0.1",
  416. BOSPort: 1234,
  417. DisableAuth: true,
  418. },
  419. inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
  420. TLVRestBlock: oscar.TLVRestBlock{
  421. TLVList: oscar.TLVList{
  422. oscar.NewTLV(oscar.TLVScreenName, "sn_user_b"),
  423. },
  424. },
  425. },
  426. mockParams: mockParams{
  427. userManagerParams: userManagerParams{
  428. getUserParams: getUserParams{
  429. {
  430. screenName: "sn_user_b",
  431. result: nil,
  432. },
  433. },
  434. },
  435. },
  436. expectOutput: oscar.SNACMessage{
  437. Frame: oscar.SNACFrame{
  438. FoodGroup: oscar.BUCP,
  439. SubGroup: oscar.BUCPChallengeResponse,
  440. },
  441. Body: oscar.SNAC_0x17_0x07_BUCPChallengeResponse{
  442. AuthKey: sessUUID.String(),
  443. },
  444. },
  445. },
  446. {
  447. name: "login with invalid username, expect failed login response (Cfg.DisableAuth=false)",
  448. cfg: config.Config{
  449. OSCARHost: "127.0.0.1",
  450. BOSPort: 1234,
  451. },
  452. inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
  453. TLVRestBlock: oscar.TLVRestBlock{
  454. TLVList: oscar.TLVList{
  455. oscar.NewTLV(oscar.TLVScreenName, "sn_user_b"),
  456. },
  457. },
  458. },
  459. mockParams: mockParams{
  460. userManagerParams: userManagerParams{
  461. getUserParams: getUserParams{
  462. {
  463. screenName: "sn_user_b",
  464. result: nil,
  465. },
  466. },
  467. },
  468. },
  469. expectOutput: oscar.SNACMessage{
  470. Frame: oscar.SNACFrame{
  471. FoodGroup: oscar.BUCP,
  472. SubGroup: oscar.BUCPLoginResponse,
  473. },
  474. Body: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
  475. TLVRestBlock: oscar.TLVRestBlock{
  476. TLVList: oscar.TLVList{
  477. oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)),
  478. },
  479. },
  480. },
  481. },
  482. },
  483. {
  484. name: "login fails on user manager lookup",
  485. inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
  486. TLVRestBlock: oscar.TLVRestBlock{
  487. TLVList: oscar.TLVList{
  488. oscar.NewTLV(oscar.TLVScreenName, "sn_user_b"),
  489. },
  490. },
  491. },
  492. mockParams: mockParams{
  493. userManagerParams: userManagerParams{
  494. getUserParams: getUserParams{
  495. {
  496. screenName: "sn_user_b",
  497. err: io.EOF,
  498. },
  499. },
  500. },
  501. },
  502. wantErr: io.EOF,
  503. },
  504. }
  505. for _, tc := range cases {
  506. t.Run(tc.name, func(t *testing.T) {
  507. userManager := newMockUserManager(t)
  508. for _, params := range tc.mockParams.getUserParams {
  509. userManager.EXPECT().
  510. User(params.screenName).
  511. Return(params.result, params.err)
  512. }
  513. svc := AuthService{
  514. config: tc.cfg,
  515. userManager: userManager,
  516. }
  517. fnNewUUID := func() uuid.UUID {
  518. return sessUUID
  519. }
  520. outputSNAC, err := svc.BUCPChallengeRequestHandler(tc.inputSNAC, fnNewUUID)
  521. assert.ErrorIs(t, err, tc.wantErr)
  522. assert.Equal(t, tc.expectOutput, outputSNAC)
  523. })
  524. }
  525. }
  526. func TestAuthService_RetrieveChatSession_HappyPath(t *testing.T) {
  527. chatID := "chat-1234"
  528. sess := newTestSession("screen-name", sessOptCannedID)
  529. sessionManager := newMockSessionManager(t)
  530. sessionManager.EXPECT().
  531. RetrieveSession(sess.ID()).
  532. Return(sess)
  533. chatRegistry := newMockChatRegistry(t)
  534. chatRegistry.EXPECT().
  535. Retrieve(chatID).
  536. Return(state.ChatRoom{}, sessionManager, nil)
  537. svc := NewAuthService(config.Config{}, nil, nil, nil, nil, chatRegistry)
  538. have, err := svc.RetrieveChatSession(chatID, sess.ID())
  539. assert.NoError(t, err)
  540. assert.Equal(t, sess, have)
  541. }
  542. func TestAuthService_RetrieveChatSession_ChatNotFound(t *testing.T) {
  543. chatID := "chat-1234"
  544. sess := newTestSession("screen-name", sessOptCannedID)
  545. chatRegistry := newMockChatRegistry(t)
  546. chatRegistry.EXPECT().
  547. Retrieve(chatID).
  548. Return(state.ChatRoom{}, nil, state.ErrChatRoomNotFound)
  549. svc := NewAuthService(config.Config{}, nil, nil, nil, nil, chatRegistry)
  550. _, err := svc.RetrieveChatSession(chatID, sess.ID())
  551. assert.ErrorIs(t, err, state.ErrChatRoomNotFound)
  552. }
  553. func TestAuthService_RetrieveChatSession_SessionNotFound(t *testing.T) {
  554. chatID := "chat-1234"
  555. sess := newTestSession("screen-name", sessOptCannedID)
  556. sessionManager := newMockSessionManager(t)
  557. sessionManager.EXPECT().
  558. RetrieveSession(sess.ID()).
  559. Return(nil)
  560. chatRegistry := newMockChatRegistry(t)
  561. chatRegistry.EXPECT().
  562. Retrieve(chatID).
  563. Return(state.ChatRoom{}, sessionManager, nil)
  564. svc := NewAuthService(config.Config{}, nil, nil, nil, nil, chatRegistry)
  565. have, err := svc.RetrieveChatSession(chatID, sess.ID())
  566. assert.NoError(t, err)
  567. assert.Nil(t, have)
  568. }
  569. func TestAuthService_RetrieveBOSSession_HappyPath(t *testing.T) {
  570. sess := newTestSession("screen-name", sessOptCannedID)
  571. sessionManager := newMockSessionManager(t)
  572. sessionManager.EXPECT().
  573. RetrieveSession(sess.ID()).
  574. Return(sess)
  575. svc := NewAuthService(config.Config{}, sessionManager, nil, nil, nil, nil)
  576. have, err := svc.RetrieveBOSSession(sess.ID())
  577. assert.NoError(t, err)
  578. assert.Equal(t, sess, have)
  579. }
  580. func TestAuthService_RetrieveBOSSession_SessionNotFound(t *testing.T) {
  581. sess := newTestSession("screen-name", sessOptCannedID)
  582. sessionManager := newMockSessionManager(t)
  583. sessionManager.EXPECT().
  584. RetrieveSession(sess.ID()).
  585. Return(nil)
  586. svc := NewAuthService(config.Config{}, sessionManager, nil, nil, nil, nil)
  587. have, err := svc.RetrieveBOSSession(sess.ID())
  588. assert.NoError(t, err)
  589. assert.Nil(t, have)
  590. }
  591. func TestAuthService_SignoutChat(t *testing.T) {
  592. sess := newTestSession("", sessOptCannedSignonTime)
  593. tests := []struct {
  594. // name is the unit test name
  595. name string
  596. // userSession is the session of the user signing out
  597. userSession *state.Session
  598. // chatRoom is the chat room user is exiting
  599. chatRoom state.ChatRoom
  600. // wantErr is the error we expect from the method
  601. wantErr error
  602. // mockParams is the list of params sent to mocks that satisfy this
  603. // method's dependencies
  604. mockParams mockParams
  605. }{
  606. {
  607. name: "user signs out of chat room, room is empty after user leaves",
  608. userSession: sess,
  609. chatRoom: state.ChatRoom{
  610. Cookie: "the-chat-cookie",
  611. },
  612. mockParams: mockParams{
  613. chatMessageRelayerParams: chatMessageRelayerParams{
  614. broadcastExceptParams: broadcastExceptParams{
  615. {
  616. except: sess,
  617. message: oscar.SNACMessage{
  618. Frame: oscar.SNACFrame{
  619. FoodGroup: oscar.Chat,
  620. SubGroup: oscar.ChatUsersLeft,
  621. },
  622. Body: oscar.SNAC_0x0E_0x04_ChatUsersLeft{
  623. Users: []oscar.TLVUserInfo{
  624. sess.TLVUserInfo(),
  625. },
  626. },
  627. },
  628. },
  629. },
  630. },
  631. sessionManagerParams: sessionManagerParams{
  632. emptyParams: emptyParams{
  633. {
  634. result: true,
  635. },
  636. },
  637. removeSessionParams: removeSessionParams{
  638. {
  639. sess: sess,
  640. },
  641. },
  642. },
  643. },
  644. },
  645. {
  646. name: "user signs out of chat room, room is not empty after user leaves",
  647. userSession: sess,
  648. chatRoom: state.ChatRoom{
  649. Cookie: "the-chat-cookie",
  650. },
  651. mockParams: mockParams{
  652. chatMessageRelayerParams: chatMessageRelayerParams{
  653. broadcastExceptParams: broadcastExceptParams{
  654. {
  655. except: sess,
  656. message: oscar.SNACMessage{
  657. Frame: oscar.SNACFrame{
  658. FoodGroup: oscar.Chat,
  659. SubGroup: oscar.ChatUsersLeft,
  660. },
  661. Body: oscar.SNAC_0x0E_0x04_ChatUsersLeft{
  662. Users: []oscar.TLVUserInfo{
  663. sess.TLVUserInfo(),
  664. },
  665. },
  666. },
  667. },
  668. },
  669. },
  670. sessionManagerParams: sessionManagerParams{
  671. emptyParams: emptyParams{
  672. {
  673. result: false,
  674. },
  675. },
  676. removeSessionParams: removeSessionParams{
  677. {
  678. sess: sess,
  679. },
  680. },
  681. },
  682. },
  683. },
  684. {
  685. name: "user can't sign out because chat room doesn't exist",
  686. userSession: sess,
  687. chatRoom: state.ChatRoom{
  688. Cookie: "the-chat-cookie",
  689. },
  690. wantErr: state.ErrChatRoomNotFound,
  691. },
  692. }
  693. for _, tt := range tests {
  694. t.Run(tt.name, func(t *testing.T) {
  695. chatMessageRelayer := newMockChatMessageRelayer(t)
  696. for _, params := range tt.mockParams.broadcastExceptParams {
  697. chatMessageRelayer.EXPECT().
  698. RelayToAllExcept(nil, params.except, params.message)
  699. }
  700. sessionManager := newMockSessionManager(t)
  701. chatRegistry := newMockChatRegistry(t)
  702. for _, params := range tt.mockParams.removeSessionParams {
  703. sessionManager.EXPECT().RemoveSession(params.sess)
  704. }
  705. for _, params := range tt.mockParams.emptyParams {
  706. sessionManager.EXPECT().Empty().Return(params.result)
  707. if params.result {
  708. chatRegistry.EXPECT().Remove(tt.chatRoom.Cookie)
  709. }
  710. }
  711. chatSessionManager := struct {
  712. ChatMessageRelayer
  713. SessionManager
  714. }{
  715. chatMessageRelayer,
  716. sessionManager,
  717. }
  718. chatRegistry.EXPECT().
  719. Retrieve(tt.chatRoom.Cookie).
  720. Return(tt.chatRoom, chatSessionManager, tt.wantErr)
  721. svc := NewAuthService(config.Config{}, nil, nil, nil, nil, chatRegistry)
  722. err := svc.SignoutChat(nil, tt.userSession, tt.chatRoom.Cookie)
  723. assert.ErrorIs(t, err, tt.wantErr)
  724. })
  725. }
  726. }
  727. func TestAuthService_Signout(t *testing.T) {
  728. sess := newTestSession("user_screen_name", sessOptCannedSignonTime)
  729. tests := []struct {
  730. // name is the unit test name
  731. name string
  732. // userSession is the session of the user signing out
  733. userSession *state.Session
  734. // chatRoom is the chat room user is exiting
  735. chatRoom state.ChatRoom
  736. // wantErr is the error we expect from the method
  737. wantErr error
  738. // mockParams is the list of params sent to mocks that satisfy this
  739. // method's dependencies
  740. mockParams mockParams
  741. }{
  742. {
  743. name: "user signs out of chat room, room is empty after user leaves",
  744. userSession: sess,
  745. chatRoom: state.ChatRoom{
  746. Cookie: "the-chat-cookie",
  747. },
  748. mockParams: mockParams{
  749. sessionManagerParams: sessionManagerParams{
  750. removeSessionParams: removeSessionParams{
  751. {
  752. sess: sess,
  753. },
  754. },
  755. },
  756. feedbagManagerParams: feedbagManagerParams{
  757. interestedUsersParams: interestedUsersParams{
  758. {
  759. screenName: "user_screen_name",
  760. users: []string{"friend1", "friend2"},
  761. },
  762. },
  763. },
  764. messageRelayerParams: messageRelayerParams{
  765. broadcastToScreenNamesParams: broadcastToScreenNamesParams{
  766. {
  767. screenNames: []string{"friend1", "friend2"},
  768. message: oscar.SNACMessage{
  769. Frame: oscar.SNACFrame{
  770. FoodGroup: oscar.Buddy,
  771. SubGroup: oscar.BuddyDeparted,
  772. },
  773. Body: oscar.SNAC_0x03_0x0C_BuddyDeparted{
  774. TLVUserInfo: oscar.TLVUserInfo{
  775. ScreenName: sess.ScreenName(),
  776. WarningLevel: sess.Warning(),
  777. },
  778. },
  779. },
  780. },
  781. },
  782. },
  783. },
  784. },
  785. {
  786. name: "user signs out of chat room, room is empty after user leaves",
  787. userSession: sess,
  788. chatRoom: state.ChatRoom{
  789. Cookie: "the-chat-cookie",
  790. },
  791. mockParams: mockParams{
  792. feedbagManagerParams: feedbagManagerParams{
  793. interestedUsersParams: interestedUsersParams{
  794. {
  795. screenName: "user_screen_name",
  796. users: []string{"friend1", "friend2"},
  797. err: io.EOF,
  798. },
  799. },
  800. },
  801. },
  802. wantErr: io.EOF,
  803. },
  804. }
  805. for _, tt := range tests {
  806. t.Run(tt.name, func(t *testing.T) {
  807. messageRelayer := newMockMessageRelayer(t)
  808. for _, params := range tt.mockParams.broadcastToScreenNamesParams {
  809. messageRelayer.EXPECT().
  810. RelayToScreenNames(mock.Anything, params.screenNames, params.message)
  811. }
  812. feedbagManager := newMockFeedbagManager(t)
  813. for _, params := range tt.mockParams.interestedUsersParams {
  814. feedbagManager.EXPECT().
  815. AdjacentUsers(params.screenName).
  816. Return(params.users, params.err)
  817. }
  818. sessionManager := newMockSessionManager(t)
  819. for _, params := range tt.mockParams.removeSessionParams {
  820. sessionManager.EXPECT().RemoveSession(params.sess)
  821. }
  822. svc := NewAuthService(config.Config{}, sessionManager, messageRelayer, feedbagManager, nil, nil)
  823. err := svc.Signout(nil, tt.userSession)
  824. assert.ErrorIs(t, err, tt.wantErr)
  825. })
  826. }
  827. }