http_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. package toc
  2. import (
  3. "encoding/base64"
  4. "io"
  5. "log/slog"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/mock"
  11. "github.com/mk6i/retro-aim-server/state"
  12. "github.com/mk6i/retro-aim-server/wire"
  13. )
  14. func TestOSCARProxy_NewServeMux(t *testing.T) {
  15. cookieBaker, err := state.NewHMACCookieBaker()
  16. assert.NoError(t, err)
  17. p := OSCARProxy{
  18. CookieBaker: cookieBaker,
  19. Logger: slog.Default(),
  20. }
  21. cookie, err := p.newHTTPAuthToken(state.NewIdentScreenName("me"))
  22. assert.NoError(t, err)
  23. cases := []struct {
  24. // name is the unit test name
  25. name string
  26. // path is the HTTP request path
  27. path string
  28. // expectedStatus is the expected HTTP response code
  29. expectedStatus int
  30. // expectedBody is the expected body payload
  31. expectedBody string
  32. // mockParams is the list of params sent to mocks that satisfy this
  33. // method's dependencies
  34. mockParams mockParams
  35. }{
  36. {
  37. name: "Successfully retrieve profile",
  38. path: "/info?from=me&user=them&cookie=" + cookie,
  39. expectedStatus: http.StatusOK,
  40. expectedBody: "My profile!",
  41. mockParams: mockParams{
  42. locateParams: locateParams{
  43. userInfoQueryParams: userInfoQueryParams{
  44. {
  45. me: state.NewIdentScreenName("me"),
  46. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  47. Type: uint16(wire.LocateTypeSig),
  48. ScreenName: "them",
  49. },
  50. msg: wire.SNACMessage{
  51. Frame: wire.SNACFrame{
  52. FoodGroup: wire.Locate,
  53. SubGroup: wire.LocateUserInfoReply,
  54. },
  55. Body: wire.SNAC_0x02_0x06_LocateUserInfoReply{
  56. TLVUserInfo: newTestSession("them").TLVUserInfo(),
  57. LocateInfo: wire.TLVRestBlock{
  58. TLVList: wire.TLVList{
  59. wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "<HTML><BODY>My profile!</BODY></HTML>"),
  60. },
  61. },
  62. },
  63. },
  64. },
  65. },
  66. },
  67. },
  68. },
  69. {
  70. name: "Retrieve profile with missing `from` query param",
  71. path: "/info?user=them&cookie=" + cookie,
  72. expectedStatus: http.StatusBadRequest,
  73. expectedBody: "required `from` param is missing",
  74. },
  75. {
  76. name: "Retrieve profile with missing `from` query param",
  77. path: "/info?from=me&cookie=" + cookie,
  78. expectedStatus: http.StatusBadRequest,
  79. expectedBody: "required `user` param is missing",
  80. },
  81. {
  82. name: "Retrieve profile with missing `cookie` query param",
  83. path: "/info?from=me&user=them",
  84. expectedStatus: http.StatusBadRequest,
  85. expectedBody: "required `cookie` param is missing",
  86. },
  87. {
  88. name: "Retrieve profile with invalid auth cookie",
  89. path: func() string {
  90. return "/info?from=me&user=them&cookie=" + base64.URLEncoding.EncodeToString([]byte("blahblah"))
  91. }(),
  92. expectedStatus: http.StatusForbidden,
  93. expectedBody: "invalid auth cookie",
  94. },
  95. {
  96. name: "Retrieve profile, receive error from locate svc",
  97. path: "/info?from=me&user=them&cookie=" + cookie,
  98. expectedStatus: http.StatusInternalServerError,
  99. expectedBody: "internal server error",
  100. mockParams: mockParams{
  101. locateParams: locateParams{
  102. userInfoQueryParams: userInfoQueryParams{
  103. {
  104. me: state.NewIdentScreenName("me"),
  105. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  106. Type: uint16(wire.LocateTypeSig),
  107. ScreenName: "them",
  108. },
  109. msg: wire.SNACMessage{
  110. Frame: wire.SNACFrame{
  111. FoodGroup: wire.Locate,
  112. SubGroup: wire.LocateUserInfoReply,
  113. },
  114. Body: wire.SNAC_0x02_0x06_LocateUserInfoReply{
  115. TLVUserInfo: newTestSession("them").TLVUserInfo(),
  116. LocateInfo: wire.TLVRestBlock{
  117. TLVList: wire.TLVList{
  118. wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "<HTML><BODY>My profile!</BODY></HTML>"),
  119. },
  120. },
  121. },
  122. },
  123. err: io.EOF,
  124. },
  125. },
  126. },
  127. },
  128. },
  129. {
  130. name: "Retrieve profile, receive unknown response from locate svc",
  131. path: "/info?from=me&user=them&cookie=" + cookie,
  132. expectedStatus: http.StatusInternalServerError,
  133. expectedBody: "internal server error",
  134. mockParams: mockParams{
  135. locateParams: locateParams{
  136. userInfoQueryParams: userInfoQueryParams{
  137. {
  138. me: state.NewIdentScreenName("me"),
  139. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  140. Type: uint16(wire.LocateTypeSig),
  141. ScreenName: "them",
  142. },
  143. msg: wire.SNACMessage{
  144. Frame: wire.SNACFrame{
  145. FoodGroup: wire.Locate,
  146. SubGroup: wire.LocateUserInfoReply,
  147. },
  148. Body: wire.SNAC_0x04_0x09_ICBMEvilReply{},
  149. },
  150. },
  151. },
  152. },
  153. },
  154. },
  155. {
  156. name: "Retrieve profile, user offline",
  157. path: "/info?from=me&user=them&cookie=" + cookie,
  158. expectedStatus: http.StatusNotFound,
  159. expectedBody: "user is unavailable",
  160. mockParams: mockParams{
  161. locateParams: locateParams{
  162. userInfoQueryParams: userInfoQueryParams{
  163. {
  164. me: state.NewIdentScreenName("me"),
  165. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  166. Type: uint16(wire.LocateTypeSig),
  167. ScreenName: "them",
  168. },
  169. msg: wire.SNACMessage{
  170. Frame: wire.SNACFrame{
  171. FoodGroup: wire.Locate,
  172. SubGroup: wire.LocateUserInfoReply,
  173. },
  174. Body: wire.SNACError{
  175. Code: wire.ErrorCodeNotLoggedOn,
  176. },
  177. },
  178. },
  179. },
  180. },
  181. },
  182. },
  183. {
  184. name: "Retrieve profile, receive error code from locate svc",
  185. path: "/info?from=me&user=them&cookie=" + cookie,
  186. expectedStatus: http.StatusInternalServerError,
  187. expectedBody: "internal server error",
  188. mockParams: mockParams{
  189. locateParams: locateParams{
  190. userInfoQueryParams: userInfoQueryParams{
  191. {
  192. me: state.NewIdentScreenName("me"),
  193. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  194. Type: uint16(wire.LocateTypeSig),
  195. ScreenName: "them",
  196. },
  197. msg: wire.SNACMessage{
  198. Frame: wire.SNACFrame{
  199. FoodGroup: wire.Locate,
  200. SubGroup: wire.LocateUserInfoReply,
  201. },
  202. Body: wire.SNACError{
  203. Code: wire.ErrorCodeInvalidSnac,
  204. },
  205. },
  206. },
  207. },
  208. },
  209. },
  210. },
  211. {
  212. name: "Successfully retrieve directory info",
  213. path: "/dir_info?user=them&cookie=" + cookie,
  214. expectedStatus: http.StatusOK,
  215. expectedBody: "their_first_name",
  216. mockParams: mockParams{
  217. locateParams: locateParams{
  218. dirInfoParams: dirInfoParams{
  219. {
  220. body: wire.SNAC_0x02_0x0B_LocateGetDirInfo{
  221. ScreenName: "them",
  222. },
  223. msg: wire.SNACMessage{
  224. Body: wire.SNAC_0x02_0x0C_LocateGetDirReply{
  225. Status: wire.LocateGetDirReplyOK,
  226. TLVBlock: wire.TLVBlock{
  227. TLVList: wire.TLVList{
  228. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  229. },
  230. },
  231. },
  232. },
  233. },
  234. },
  235. },
  236. },
  237. },
  238. {
  239. name: "Retrieve directory info, no results found",
  240. path: "/dir_info?user=them&cookie=" + cookie,
  241. expectedStatus: http.StatusNotFound,
  242. expectedBody: "no user directory info found",
  243. mockParams: mockParams{
  244. locateParams: locateParams{
  245. dirInfoParams: dirInfoParams{
  246. {
  247. body: wire.SNAC_0x02_0x0B_LocateGetDirInfo{
  248. ScreenName: "them",
  249. },
  250. msg: wire.SNACMessage{
  251. Body: wire.SNAC_0x02_0x0C_LocateGetDirReply{
  252. Status: wire.LocateGetDirReplyOK,
  253. TLVBlock: wire.TLVBlock{
  254. TLVList: wire.TLVList{},
  255. },
  256. },
  257. },
  258. },
  259. },
  260. },
  261. },
  262. },
  263. {
  264. name: "Retrieve directory info, unexpected response from locate svc",
  265. path: "/dir_info?user=them&cookie=" + cookie,
  266. expectedStatus: http.StatusInternalServerError,
  267. expectedBody: "internal server error",
  268. mockParams: mockParams{
  269. locateParams: locateParams{
  270. dirInfoParams: dirInfoParams{
  271. {
  272. body: wire.SNAC_0x02_0x0B_LocateGetDirInfo{
  273. ScreenName: "them",
  274. },
  275. msg: wire.SNACMessage{
  276. Body: wire.SNACError{},
  277. },
  278. },
  279. },
  280. },
  281. },
  282. },
  283. {
  284. name: "Search directory with missing `user` query param",
  285. path: "/dir_info?cookie=" + cookie,
  286. expectedStatus: http.StatusBadRequest,
  287. expectedBody: "required `user` param is missing",
  288. },
  289. {
  290. name: "Retrieve directory info, receive err from locate svc",
  291. path: "/dir_info?user=them&cookie=" + cookie,
  292. expectedStatus: http.StatusInternalServerError,
  293. expectedBody: "internal server error",
  294. mockParams: mockParams{
  295. locateParams: locateParams{
  296. dirInfoParams: dirInfoParams{
  297. {
  298. body: wire.SNAC_0x02_0x0B_LocateGetDirInfo{
  299. ScreenName: "them",
  300. },
  301. msg: wire.SNACMessage{
  302. Body: wire.SNAC_0x02_0x0C_LocateGetDirReply{
  303. Status: wire.LocateGetDirReplyOK,
  304. TLVBlock: wire.TLVBlock{
  305. TLVList: wire.TLVList{
  306. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  307. },
  308. },
  309. },
  310. },
  311. err: io.EOF,
  312. },
  313. },
  314. },
  315. },
  316. },
  317. {
  318. name: "Successfully search directory by name and address",
  319. path: "/dir_search?first_name=their_first_name" +
  320. "&middle_name=their_middle_name" +
  321. "&last_name=their_last_name" +
  322. "&maiden_name=their_maiden_name" +
  323. "&city=their_city" +
  324. "&state=their_state" +
  325. "&country=their_country" +
  326. "&cookie=" + cookie,
  327. expectedStatus: http.StatusOK,
  328. expectedBody: "their_first_name",
  329. mockParams: mockParams{
  330. dirSearchParams: dirSearchParams{
  331. infoQueryParams: infoQueryParams{
  332. {
  333. inBody: wire.SNAC_0x0F_0x02_InfoQuery{
  334. TLVRestBlock: wire.TLVRestBlock{
  335. TLVList: wire.TLVList{
  336. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  337. wire.NewTLVBE(wire.ODirTLVMiddleName, "their_middle_name"),
  338. wire.NewTLVBE(wire.ODirTLVLastName, "their_last_name"),
  339. wire.NewTLVBE(wire.ODirTLVMaidenName, "their_maiden_name"),
  340. wire.NewTLVBE(wire.ODirTLVCity, "their_city"),
  341. wire.NewTLVBE(wire.ODirTLVState, "their_state"),
  342. wire.NewTLVBE(wire.ODirTLVCountry, "their_country"),
  343. },
  344. },
  345. },
  346. msg: wire.SNACMessage{
  347. Body: wire.SNAC_0x0F_0x03_InfoReply{
  348. Status: wire.ODirSearchResponseOK,
  349. Results: struct {
  350. List []wire.TLVBlock `oscar:"count_prefix=uint16"`
  351. }{
  352. List: []wire.TLVBlock{
  353. {
  354. TLVList: wire.TLVList{
  355. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  356. },
  357. },
  358. },
  359. },
  360. },
  361. },
  362. },
  363. },
  364. },
  365. },
  366. },
  367. {
  368. name: "Successfully search directory by email",
  369. path: "/dir_search?email=their_email@aol.com&cookie=" + cookie,
  370. expectedStatus: http.StatusOK,
  371. expectedBody: "their_first_name",
  372. mockParams: mockParams{
  373. dirSearchParams: dirSearchParams{
  374. infoQueryParams: infoQueryParams{
  375. {
  376. inBody: wire.SNAC_0x0F_0x02_InfoQuery{
  377. TLVRestBlock: wire.TLVRestBlock{
  378. TLVList: wire.TLVList{
  379. wire.NewTLVBE(wire.ODirTLVEmailAddress, "their_email@aol.com"),
  380. },
  381. },
  382. },
  383. msg: wire.SNACMessage{
  384. Body: wire.SNAC_0x0F_0x03_InfoReply{
  385. Status: wire.ODirSearchResponseOK,
  386. Results: struct {
  387. List []wire.TLVBlock `oscar:"count_prefix=uint16"`
  388. }{
  389. List: []wire.TLVBlock{
  390. {
  391. TLVList: wire.TLVList{
  392. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  393. },
  394. },
  395. },
  396. },
  397. },
  398. },
  399. },
  400. },
  401. },
  402. },
  403. },
  404. {
  405. name: "Successfully search directory by keyword",
  406. path: "/dir_search?keyword=their_keyword&cookie=" + cookie,
  407. expectedStatus: http.StatusOK,
  408. expectedBody: "their_first_name",
  409. mockParams: mockParams{
  410. dirSearchParams: dirSearchParams{
  411. infoQueryParams: infoQueryParams{
  412. {
  413. inBody: wire.SNAC_0x0F_0x02_InfoQuery{
  414. TLVRestBlock: wire.TLVRestBlock{
  415. TLVList: wire.TLVList{
  416. wire.NewTLVBE(wire.ODirTLVInterest, "their_keyword"),
  417. },
  418. },
  419. },
  420. msg: wire.SNACMessage{
  421. Body: wire.SNAC_0x0F_0x03_InfoReply{
  422. Status: wire.ODirSearchResponseOK,
  423. Results: struct {
  424. List []wire.TLVBlock `oscar:"count_prefix=uint16"`
  425. }{
  426. List: []wire.TLVBlock{
  427. {
  428. TLVList: wire.TLVList{
  429. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  430. },
  431. },
  432. },
  433. },
  434. },
  435. },
  436. },
  437. },
  438. },
  439. },
  440. },
  441. {
  442. name: "Search directory by email, receive err from dir search svc",
  443. path: "/dir_search?email=their_email@aol.com&cookie=" + cookie,
  444. expectedStatus: http.StatusInternalServerError,
  445. expectedBody: "internal server error",
  446. mockParams: mockParams{
  447. dirSearchParams: dirSearchParams{
  448. infoQueryParams: infoQueryParams{
  449. {
  450. inBody: wire.SNAC_0x0F_0x02_InfoQuery{
  451. TLVRestBlock: wire.TLVRestBlock{
  452. TLVList: wire.TLVList{
  453. wire.NewTLVBE(wire.ODirTLVEmailAddress, "their_email@aol.com"),
  454. },
  455. },
  456. },
  457. msg: wire.SNACMessage{
  458. Body: wire.SNAC_0x0F_0x03_InfoReply{
  459. Status: wire.ODirSearchResponseOK,
  460. Results: struct {
  461. List []wire.TLVBlock `oscar:"count_prefix=uint16"`
  462. }{
  463. List: []wire.TLVBlock{
  464. {
  465. TLVList: wire.TLVList{
  466. wire.NewTLVBE(wire.ODirTLVFirstName, "their_first_name"),
  467. },
  468. },
  469. },
  470. },
  471. },
  472. },
  473. err: io.EOF,
  474. },
  475. },
  476. },
  477. },
  478. },
  479. {
  480. name: "Search directory by email, receive unknown response from dir search svc",
  481. path: "/dir_search?email=their_email@aol.com&cookie=" + cookie,
  482. expectedStatus: http.StatusInternalServerError,
  483. expectedBody: "internal server error",
  484. mockParams: mockParams{
  485. dirSearchParams: dirSearchParams{
  486. infoQueryParams: infoQueryParams{
  487. {
  488. inBody: wire.SNAC_0x0F_0x02_InfoQuery{
  489. TLVRestBlock: wire.TLVRestBlock{
  490. TLVList: wire.TLVList{
  491. wire.NewTLVBE(wire.ODirTLVEmailAddress, "their_email@aol.com"),
  492. },
  493. },
  494. },
  495. msg: wire.SNACMessage{
  496. Body: wire.SNACError{},
  497. },
  498. },
  499. },
  500. },
  501. },
  502. },
  503. {
  504. name: "Search directory without any correct search parameters",
  505. path: "/dir_search?cookie=" + cookie,
  506. expectedStatus: http.StatusBadRequest,
  507. expectedBody: "missing search parameters",
  508. mockParams: mockParams{
  509. dirSearchParams: dirSearchParams{
  510. infoQueryParams: infoQueryParams{
  511. {
  512. inBody: wire.SNAC_0x0F_0x02_InfoQuery{},
  513. msg: wire.SNACMessage{
  514. Body: wire.SNAC_0x0F_0x03_InfoReply{
  515. Status: wire.ODirSearchResponseNameMissing,
  516. },
  517. },
  518. },
  519. },
  520. },
  521. },
  522. },
  523. {
  524. name: "Search directory, receive error code from search dir svc",
  525. path: "/dir_search?cookie=" + cookie,
  526. expectedStatus: http.StatusInternalServerError,
  527. expectedBody: "internal server error",
  528. mockParams: mockParams{
  529. dirSearchParams: dirSearchParams{
  530. infoQueryParams: infoQueryParams{
  531. {
  532. inBody: wire.SNAC_0x0F_0x02_InfoQuery{},
  533. msg: wire.SNACMessage{
  534. Body: wire.SNAC_0x0F_0x03_InfoReply{
  535. Status: wire.ODirSearchResponseUnavailable1,
  536. },
  537. },
  538. },
  539. },
  540. },
  541. },
  542. },
  543. {
  544. name: "Search directory, receive unknown response from search dir svc",
  545. path: "/dir_search?cookie=" + cookie,
  546. expectedStatus: http.StatusInternalServerError,
  547. expectedBody: "internal server error",
  548. mockParams: mockParams{
  549. dirSearchParams: dirSearchParams{
  550. infoQueryParams: infoQueryParams{
  551. {
  552. inBody: wire.SNAC_0x0F_0x02_InfoQuery{},
  553. msg: wire.SNACMessage{
  554. Body: wire.SNAC_0x0F_0x03_InfoReply{
  555. Status: wire.ODirSearchResponseUnavailable1,
  556. },
  557. },
  558. },
  559. },
  560. },
  561. },
  562. },
  563. }
  564. for _, tc := range cases {
  565. t.Run(tc.name, func(t *testing.T) {
  566. locateSvc := newMockLocateService(t)
  567. for _, params := range tc.mockParams.userInfoQueryParams {
  568. locateSvc.EXPECT().
  569. UserInfoQuery(mock.Anything, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  570. Return(params.msg, params.err)
  571. }
  572. for _, params := range tc.mockParams.dirInfoParams {
  573. locateSvc.EXPECT().
  574. DirInfo(mock.Anything, wire.SNACFrame{}, params.body).
  575. Return(params.msg, params.err)
  576. }
  577. dirSearchSvc := newMockDirSearchService(t)
  578. for _, params := range tc.mockParams.infoQueryParams {
  579. dirSearchSvc.EXPECT().
  580. InfoQuery(mock.Anything, wire.SNACFrame{}, params.inBody).
  581. Return(params.msg, params.err)
  582. }
  583. svc := OSCARProxy{
  584. CookieBaker: cookieBaker,
  585. DirSearchService: dirSearchSvc,
  586. LocateService: locateSvc,
  587. Logger: slog.Default(),
  588. }
  589. req, err := http.NewRequest(http.MethodGet, tc.path, nil)
  590. if err != nil {
  591. t.Fatalf("Failed to create request: %v", err)
  592. }
  593. rr := httptest.NewRecorder()
  594. svc.NewServeMux().ServeHTTP(rr, req)
  595. assert.Equal(t, tc.expectedStatus, rr.Code)
  596. assert.Contains(t, rr.Body.String(), tc.expectedBody)
  597. })
  598. }
  599. }