mgmt_api_test.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377
  1. package http
  2. import (
  3. "io"
  4. "log/slog"
  5. "net/http"
  6. "net/http/httptest"
  7. "net/mail"
  8. "strings"
  9. "testing"
  10. "time"
  11. "github.com/google/uuid"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/mock"
  14. "github.com/mk6i/retro-aim-server/config"
  15. "github.com/mk6i/retro-aim-server/state"
  16. "github.com/mk6i/retro-aim-server/wire"
  17. )
  18. func TestSessionHandler_GET(t *testing.T) {
  19. fnNewSess := func(screenName string, uin uint32) *state.Session {
  20. sess := state.NewSession()
  21. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  22. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  23. sess.SetUIN(uin)
  24. return sess
  25. }
  26. tt := []struct {
  27. name string
  28. want string
  29. statusCode int
  30. timeSinceFunc func(t time.Time) time.Duration
  31. mockParams mockParams
  32. }{
  33. {
  34. name: "without sessions",
  35. want: `{"count":0,"sessions":[]}`,
  36. statusCode: http.StatusOK,
  37. mockParams: mockParams{
  38. sessionRetrieverParams: sessionRetrieverParams{
  39. sessionRetrieverAllSessionsParams: sessionRetrieverAllSessionsParams{
  40. {
  41. result: []*state.Session{},
  42. },
  43. },
  44. },
  45. },
  46. },
  47. {
  48. name: "with sessions",
  49. want: `{"count":3,"sessions":[{"id":"usera","screen_name":"userA","online_seconds":0,"away_message":"","idle_seconds":0,"is_icq":false},{"id":"userb","screen_name":"userB","online_seconds":0,"away_message":"","idle_seconds":0,"is_icq":false},{"id":"100003","screen_name":"100003","online_seconds":0,"away_message":"","idle_seconds":0,"is_icq":true}]}`,
  50. statusCode: http.StatusOK,
  51. timeSinceFunc: func(t time.Time) time.Duration { t0 := time.Now(); return t0.Sub(t0) },
  52. mockParams: mockParams{
  53. sessionRetrieverParams: sessionRetrieverParams{
  54. sessionRetrieverAllSessionsParams: sessionRetrieverAllSessionsParams{
  55. {
  56. result: []*state.Session{
  57. fnNewSess("userA", 0),
  58. fnNewSess("userB", 0),
  59. fnNewSess("100003", 100003),
  60. },
  61. },
  62. },
  63. },
  64. },
  65. },
  66. }
  67. for _, tc := range tt {
  68. t.Run(tc.name, func(t *testing.T) {
  69. request := httptest.NewRequest(http.MethodGet, "/session", nil)
  70. responseRecorder := httptest.NewRecorder()
  71. sessionRetriever := newMockSessionRetriever(t)
  72. for _, params := range tc.mockParams.sessionRetrieverParams.sessionRetrieverAllSessionsParams {
  73. sessionRetriever.EXPECT().
  74. AllSessions().
  75. Return(params.result)
  76. }
  77. getSessionHandler(responseRecorder, request, sessionRetriever, tc.timeSinceFunc)
  78. if responseRecorder.Code != tc.statusCode {
  79. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  80. }
  81. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  82. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  83. }
  84. })
  85. }
  86. }
  87. func TestSessionHandlerScreenname_GET(t *testing.T) {
  88. fnNewSess := func(screenName string, uin uint32) *state.Session {
  89. sess := state.NewSession()
  90. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  91. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  92. sess.SetUIN(uin)
  93. return sess
  94. }
  95. tt := []struct {
  96. name string
  97. sessions []*state.Session
  98. requestScreenName state.IdentScreenName
  99. want string
  100. statusCode int
  101. timeSinceFunc func(t time.Time) time.Duration
  102. mockParams mockParams
  103. }{
  104. {
  105. name: "no session for screenname",
  106. sessions: []*state.Session{},
  107. requestScreenName: state.NewIdentScreenName("userA"),
  108. want: `session not found`,
  109. statusCode: http.StatusNotFound,
  110. mockParams: mockParams{
  111. sessionRetrieverParams: sessionRetrieverParams{
  112. retrieveSessionByNameParams: retrieveSessionByNameParams{
  113. {
  114. screenName: state.NewIdentScreenName("userA"),
  115. result: nil,
  116. },
  117. },
  118. },
  119. },
  120. },
  121. {
  122. name: "active session found for screenname",
  123. requestScreenName: state.NewIdentScreenName("userA"),
  124. want: `{"count":1,"sessions":[{"id":"usera","screen_name":"userA","online_seconds":0,"away_message":"","idle_seconds":0,"is_icq":false}]}`,
  125. statusCode: http.StatusOK,
  126. timeSinceFunc: func(t time.Time) time.Duration { t0 := time.Now(); return t0.Sub(t0) },
  127. mockParams: mockParams{
  128. sessionRetrieverParams: sessionRetrieverParams{
  129. retrieveSessionByNameParams: retrieveSessionByNameParams{
  130. {
  131. screenName: state.NewIdentScreenName("userA"),
  132. result: fnNewSess("userA", 0),
  133. },
  134. },
  135. },
  136. },
  137. },
  138. }
  139. for _, tc := range tt {
  140. t.Run(tc.name, func(t *testing.T) {
  141. request := httptest.NewRequest(http.MethodGet, "/session/"+tc.requestScreenName.String(), nil)
  142. request.SetPathValue("screenname", tc.requestScreenName.String())
  143. responseRecorder := httptest.NewRecorder()
  144. sessionRetriever := newMockSessionRetriever(t)
  145. for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionByNameParams {
  146. sessionRetriever.EXPECT().
  147. RetrieveByScreenName(params.screenName).
  148. Return(params.result)
  149. }
  150. getSessionHandler(responseRecorder, request, sessionRetriever, tc.timeSinceFunc)
  151. if responseRecorder.Code != tc.statusCode {
  152. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  153. }
  154. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  155. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  156. }
  157. })
  158. }
  159. }
  160. func TestUserAccountHandler_GET(t *testing.T) {
  161. tt := []struct {
  162. name string
  163. requestScreenName state.IdentScreenName
  164. want string
  165. statusCode int
  166. mockParams mockParams
  167. }{
  168. {
  169. name: "invalid account",
  170. requestScreenName: state.NewIdentScreenName("userA"),
  171. want: `user not found`,
  172. statusCode: http.StatusNotFound,
  173. mockParams: mockParams{
  174. userManagerParams: userManagerParams{
  175. getUserParams: getUserParams{
  176. {
  177. screenName: state.NewIdentScreenName("userA"),
  178. result: nil,
  179. },
  180. },
  181. },
  182. },
  183. },
  184. {
  185. name: "valid aim account",
  186. requestScreenName: state.NewIdentScreenName("userA"),
  187. want: `{"id":"usera","screen_name":"userA","profile":"My Profile Text","email_address":"\u003cuserA@aol.com\u003e","reg_status":2,"confirmed":true,"is_icq":false}`,
  188. statusCode: http.StatusOK,
  189. mockParams: mockParams{
  190. userManagerParams: userManagerParams{
  191. getUserParams: getUserParams{
  192. {
  193. screenName: state.NewIdentScreenName("userA"),
  194. result: &state.User{
  195. DisplayScreenName: "userA",
  196. IdentScreenName: state.NewIdentScreenName("userA"),
  197. },
  198. },
  199. },
  200. },
  201. accountRetrieverParams: accountRetrieverParams{
  202. emailAddressByNameParams: emailAddressByNameParams{
  203. {
  204. screenName: state.NewIdentScreenName("userA"),
  205. result: &mail.Address{
  206. Address: "userA@aol.com",
  207. },
  208. },
  209. },
  210. regStatusByNameParams: regStatusByNameParams{
  211. {
  212. screenName: state.NewIdentScreenName("userA"),
  213. result: uint16(0x02),
  214. },
  215. },
  216. confirmStatusByNameParams: confirmStatusByNameParams{
  217. {
  218. screenName: state.NewIdentScreenName("userA"),
  219. result: true,
  220. },
  221. },
  222. },
  223. profileRetrieverParams: profileRetrieverParams{
  224. retrieveProfileParams: retrieveProfileParams{
  225. {
  226. screenName: state.NewIdentScreenName("userA"),
  227. result: "My Profile Text",
  228. },
  229. },
  230. },
  231. },
  232. },
  233. }
  234. for _, tc := range tt {
  235. t.Run(tc.name, func(t *testing.T) {
  236. request := httptest.NewRequest(http.MethodGet, "/user/"+tc.requestScreenName.String()+"/account", nil)
  237. request.SetPathValue("screenname", tc.requestScreenName.String())
  238. responseRecorder := httptest.NewRecorder()
  239. userManager := newMockUserManager(t)
  240. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  241. userManager.EXPECT().
  242. User(params.screenName).
  243. Return(params.result, params.err)
  244. }
  245. accountRetriever := newMockAccountRetriever(t)
  246. for _, params := range tc.mockParams.accountRetrieverParams.emailAddressByNameParams {
  247. accountRetriever.EXPECT().
  248. EmailAddressByName(params.screenName).
  249. Return(params.result, params.err)
  250. }
  251. for _, params := range tc.mockParams.accountRetrieverParams.regStatusByNameParams {
  252. accountRetriever.EXPECT().
  253. RegStatusByName(params.screenName).
  254. Return(params.result, params.err)
  255. }
  256. for _, params := range tc.mockParams.accountRetrieverParams.confirmStatusByNameParams {
  257. accountRetriever.EXPECT().
  258. ConfirmStatusByName(params.screenName).
  259. Return(params.result, params.err)
  260. }
  261. profileRetriever := newMockProfileRetriever(t)
  262. for _, params := range tc.mockParams.profileRetrieverParams.retrieveProfileParams {
  263. profileRetriever.EXPECT().
  264. Profile(params.screenName).
  265. Return(params.result, params.err)
  266. }
  267. getUserAccountHandler(responseRecorder, request, userManager, accountRetriever, profileRetriever, slog.Default())
  268. if responseRecorder.Code != tc.statusCode {
  269. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  270. }
  271. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  272. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  273. }
  274. })
  275. }
  276. }
  277. func TestUserBuddyIconHandler_GET(t *testing.T) {
  278. sampleGIF := []byte{
  279. 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x32, 0x00, 0x32, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
  280. 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
  281. 0x32, 0x00, 0x32, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b}
  282. sampleJPG := []byte{0xFF, 0xD8, 0xFF, 0x43, 0x13, 0x37}
  283. tt := []struct {
  284. name string
  285. requestScreenName state.IdentScreenName
  286. want string
  287. statusCode int
  288. contentType string
  289. mockParams mockParams
  290. }{
  291. {
  292. name: "invalid account",
  293. requestScreenName: state.NewIdentScreenName("userA"),
  294. want: `user not found`,
  295. statusCode: http.StatusNotFound,
  296. contentType: "text/plain; charset=utf-8",
  297. mockParams: mockParams{
  298. userManagerParams: userManagerParams{
  299. getUserParams: getUserParams{
  300. {
  301. screenName: state.NewIdentScreenName("userA"),
  302. result: nil,
  303. },
  304. },
  305. },
  306. },
  307. },
  308. {
  309. name: "account with gif buddy icon",
  310. requestScreenName: state.NewIdentScreenName("userA"),
  311. want: string(sampleGIF),
  312. statusCode: http.StatusOK,
  313. contentType: "image/gif",
  314. mockParams: mockParams{
  315. userManagerParams: userManagerParams{
  316. getUserParams: getUserParams{
  317. {
  318. screenName: state.NewIdentScreenName("userA"),
  319. result: &state.User{
  320. DisplayScreenName: "userA",
  321. IdentScreenName: state.NewIdentScreenName("userA"),
  322. },
  323. },
  324. },
  325. },
  326. feedBagRetrieverParams: feedBagRetrieverParams{
  327. buddyIconRefByNameParams: buddyIconRefByNameParams{
  328. {
  329. screenName: state.NewIdentScreenName("userA"),
  330. result: &wire.BARTID{
  331. Type: wire.BARTTypesBuddyIcon,
  332. BARTInfo: wire.BARTInfo{
  333. Flags: 0x00,
  334. Hash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  335. },
  336. },
  337. },
  338. },
  339. },
  340. bartRetrieverParams: bartRetrieverParams{
  341. bartRetrieveParams: bartRetrieveParams{
  342. {
  343. itemHash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  344. result: sampleGIF,
  345. },
  346. },
  347. },
  348. },
  349. },
  350. {
  351. name: "account with jpg buddy icon",
  352. requestScreenName: state.NewIdentScreenName("userA"),
  353. want: string(sampleJPG),
  354. statusCode: http.StatusOK,
  355. contentType: "image/jpeg",
  356. mockParams: mockParams{
  357. userManagerParams: userManagerParams{
  358. getUserParams: getUserParams{
  359. {
  360. screenName: state.NewIdentScreenName("userA"),
  361. result: &state.User{
  362. DisplayScreenName: "userA",
  363. IdentScreenName: state.NewIdentScreenName("userA"),
  364. },
  365. },
  366. },
  367. },
  368. feedBagRetrieverParams: feedBagRetrieverParams{
  369. buddyIconRefByNameParams: buddyIconRefByNameParams{
  370. {
  371. screenName: state.NewIdentScreenName("userA"),
  372. result: &wire.BARTID{
  373. Type: wire.BARTTypesBuddyIcon,
  374. BARTInfo: wire.BARTInfo{
  375. Flags: 0x00,
  376. Hash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  377. },
  378. },
  379. },
  380. },
  381. },
  382. bartRetrieverParams: bartRetrieverParams{
  383. bartRetrieveParams: bartRetrieveParams{
  384. {
  385. itemHash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  386. result: sampleJPG,
  387. },
  388. },
  389. },
  390. },
  391. },
  392. {
  393. name: "account with unknown format buddy icon",
  394. requestScreenName: state.NewIdentScreenName("userA"),
  395. want: string([]byte{0x13, 0x37, 0x13, 0x37, 0x13, 0x37}),
  396. statusCode: http.StatusOK,
  397. contentType: "application/octet-stream",
  398. mockParams: mockParams{
  399. userManagerParams: userManagerParams{
  400. getUserParams: getUserParams{
  401. {
  402. screenName: state.NewIdentScreenName("userA"),
  403. result: &state.User{
  404. DisplayScreenName: "userA",
  405. IdentScreenName: state.NewIdentScreenName("userA"),
  406. },
  407. },
  408. },
  409. },
  410. feedBagRetrieverParams: feedBagRetrieverParams{
  411. buddyIconRefByNameParams: buddyIconRefByNameParams{
  412. {
  413. screenName: state.NewIdentScreenName("userA"),
  414. result: &wire.BARTID{
  415. Type: wire.BARTTypesBuddyIcon,
  416. BARTInfo: wire.BARTInfo{
  417. Flags: 0x00,
  418. Hash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  419. },
  420. },
  421. },
  422. },
  423. },
  424. bartRetrieverParams: bartRetrieverParams{
  425. bartRetrieveParams: bartRetrieveParams{
  426. {
  427. itemHash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  428. result: []byte{0x13, 0x37, 0x13, 0x37, 0x13, 0x37},
  429. },
  430. },
  431. },
  432. },
  433. },
  434. {
  435. name: "account with cleared buddy icon",
  436. requestScreenName: state.NewIdentScreenName("userA"),
  437. want: "icon not found",
  438. statusCode: http.StatusNotFound,
  439. contentType: "text/plain; charset=utf-8",
  440. mockParams: mockParams{
  441. userManagerParams: userManagerParams{
  442. getUserParams: getUserParams{
  443. {
  444. screenName: state.NewIdentScreenName("userA"),
  445. result: &state.User{
  446. DisplayScreenName: "userA",
  447. IdentScreenName: state.NewIdentScreenName("userA"),
  448. },
  449. },
  450. },
  451. },
  452. feedBagRetrieverParams: feedBagRetrieverParams{
  453. buddyIconRefByNameParams: buddyIconRefByNameParams{
  454. {
  455. screenName: state.NewIdentScreenName("userA"),
  456. result: &wire.BARTID{
  457. Type: wire.BARTTypesBuddyIcon,
  458. BARTInfo: wire.BARTInfo{
  459. Flags: 0x00,
  460. Hash: wire.GetClearIconHash(),
  461. },
  462. },
  463. },
  464. },
  465. },
  466. },
  467. },
  468. {
  469. name: "account with no buddy icon",
  470. requestScreenName: state.NewIdentScreenName("userA"),
  471. want: "icon not found",
  472. statusCode: http.StatusNotFound,
  473. contentType: "text/plain; charset=utf-8",
  474. mockParams: mockParams{
  475. userManagerParams: userManagerParams{
  476. getUserParams: getUserParams{
  477. {
  478. screenName: state.NewIdentScreenName("userA"),
  479. result: &state.User{
  480. DisplayScreenName: "userA",
  481. IdentScreenName: state.NewIdentScreenName("userA"),
  482. },
  483. },
  484. },
  485. },
  486. feedBagRetrieverParams: feedBagRetrieverParams{
  487. buddyIconRefByNameParams: buddyIconRefByNameParams{
  488. {
  489. screenName: state.NewIdentScreenName("userA"),
  490. result: nil,
  491. },
  492. },
  493. },
  494. },
  495. },
  496. }
  497. for _, tc := range tt {
  498. t.Run(tc.name, func(t *testing.T) {
  499. request := httptest.NewRequest(http.MethodGet, "/user/"+tc.requestScreenName.String()+"/icon", nil)
  500. request.SetPathValue("screenname", tc.requestScreenName.String())
  501. responseRecorder := httptest.NewRecorder()
  502. userManager := newMockUserManager(t)
  503. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  504. userManager.EXPECT().
  505. User(params.screenName).
  506. Return(params.result, params.err)
  507. }
  508. feedbagRetriever := newMockFeedBagRetriever(t)
  509. for _, params := range tc.mockParams.feedBagRetrieverParams.buddyIconRefByNameParams {
  510. feedbagRetriever.EXPECT().
  511. BuddyIconRefByName(params.screenName).
  512. Return(params.result, params.err)
  513. }
  514. bartRetriever := newMockBARTRetriever(t)
  515. for _, params := range tc.mockParams.bartRetrieverParams.bartRetrieveParams {
  516. bartRetriever.EXPECT().
  517. BARTRetrieve(params.itemHash).
  518. Return(params.result, params.err)
  519. }
  520. getUserBuddyIconHandler(responseRecorder, request, userManager, feedbagRetriever, bartRetriever, slog.Default())
  521. if responseRecorder.Code != tc.statusCode {
  522. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  523. }
  524. contentType := responseRecorder.Header().Get("Content-Type")
  525. if contentType != tc.contentType {
  526. t.Errorf("Want content type '%s', got '%s'", tc.contentType, contentType)
  527. }
  528. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  529. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  530. }
  531. })
  532. }
  533. }
  534. func TestUserHandler_GET(t *testing.T) {
  535. tt := []struct {
  536. name string
  537. want string
  538. statusCode int
  539. mockParams mockParams
  540. }{
  541. {
  542. name: "empty user store",
  543. want: `[]`,
  544. statusCode: http.StatusOK,
  545. mockParams: mockParams{
  546. userManagerParams: userManagerParams{
  547. allUsersParams: allUsersParams{
  548. {
  549. result: []state.User{},
  550. },
  551. },
  552. },
  553. },
  554. },
  555. {
  556. name: "user store containing 3 users",
  557. want: `[{"id":"usera","screen_name":"userA","is_icq":false},{"id":"userb","screen_name":"userB","is_icq":false},{"id":"100003","screen_name":"100003","is_icq":true}]`,
  558. statusCode: http.StatusOK,
  559. mockParams: mockParams{
  560. userManagerParams: userManagerParams{
  561. allUsersParams: allUsersParams{
  562. {
  563. result: []state.User{
  564. {
  565. DisplayScreenName: "userA",
  566. IdentScreenName: state.NewIdentScreenName("userA"),
  567. },
  568. {
  569. DisplayScreenName: "userB",
  570. IdentScreenName: state.NewIdentScreenName("userB"),
  571. },
  572. {
  573. DisplayScreenName: "100003",
  574. IdentScreenName: state.NewIdentScreenName("100003"),
  575. IsICQ: true,
  576. },
  577. },
  578. },
  579. },
  580. },
  581. },
  582. },
  583. {
  584. name: "user handler error",
  585. want: `internal server error`,
  586. statusCode: http.StatusInternalServerError,
  587. mockParams: mockParams{
  588. userManagerParams: userManagerParams{
  589. allUsersParams: allUsersParams{
  590. {
  591. result: []state.User{},
  592. err: io.EOF,
  593. },
  594. },
  595. },
  596. },
  597. },
  598. }
  599. for _, tc := range tt {
  600. t.Run(tc.name, func(t *testing.T) {
  601. responseRecorder := httptest.NewRecorder()
  602. userManager := newMockUserManager(t)
  603. for _, params := range tc.mockParams.userManagerParams.allUsersParams {
  604. userManager.EXPECT().
  605. AllUsers().
  606. Return(params.result, params.err)
  607. }
  608. getUserHandler(responseRecorder, userManager, slog.Default())
  609. if responseRecorder.Code != tc.statusCode {
  610. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  611. }
  612. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  613. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  614. }
  615. })
  616. }
  617. }
  618. func TestUserHandler_POST(t *testing.T) {
  619. tt := []struct {
  620. name string
  621. body string
  622. UUID uuid.UUID
  623. want string
  624. password string
  625. statusCode int
  626. mockParams mockParams
  627. }{
  628. {
  629. name: "with valid AIM user",
  630. body: `{"screen_name":"userA", "password":"thepassword"}`,
  631. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  632. want: `User account created successfully.`,
  633. password: "thepassword",
  634. statusCode: http.StatusCreated,
  635. mockParams: mockParams{
  636. userManagerParams: userManagerParams{
  637. insertUserParams: insertUserParams{
  638. {
  639. u: state.User{
  640. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  641. DisplayScreenName: "userA",
  642. IdentScreenName: state.NewIdentScreenName("userA"),
  643. },
  644. err: nil,
  645. },
  646. },
  647. },
  648. },
  649. },
  650. {
  651. name: "with valid ICQ user",
  652. body: `{"screen_name":"100003", "password":"thepass"}`,
  653. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  654. want: `User account created successfully.`,
  655. password: "thepass",
  656. statusCode: http.StatusCreated,
  657. mockParams: mockParams{
  658. userManagerParams: userManagerParams{
  659. insertUserParams: insertUserParams{
  660. {
  661. u: state.User{
  662. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  663. DisplayScreenName: "100003",
  664. IdentScreenName: state.NewIdentScreenName("100003"),
  665. IsICQ: true,
  666. },
  667. err: nil,
  668. },
  669. },
  670. },
  671. },
  672. },
  673. {
  674. name: "with malformed body",
  675. body: `{"screen_name":"userA", "password":"thepassword"`, // missing closing }
  676. want: `malformed input`,
  677. statusCode: http.StatusBadRequest,
  678. },
  679. {
  680. name: "user handler error",
  681. body: `{"screen_name":"userA", "password":"thepassword"}`,
  682. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  683. want: `internal server error`,
  684. password: "thepassword",
  685. statusCode: http.StatusInternalServerError,
  686. mockParams: mockParams{
  687. userManagerParams: userManagerParams{
  688. insertUserParams: insertUserParams{
  689. {
  690. u: state.User{
  691. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  692. DisplayScreenName: "userA",
  693. IdentScreenName: state.NewIdentScreenName("userA"),
  694. },
  695. err: io.EOF,
  696. },
  697. },
  698. },
  699. },
  700. },
  701. {
  702. name: "duplicate user",
  703. body: `{"screen_name":"userA", "password":"thepassword"}`,
  704. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  705. want: `user already exists`,
  706. password: "thepassword",
  707. statusCode: http.StatusConflict,
  708. mockParams: mockParams{
  709. userManagerParams: userManagerParams{
  710. insertUserParams: insertUserParams{
  711. {
  712. u: state.User{
  713. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  714. DisplayScreenName: "userA",
  715. IdentScreenName: state.NewIdentScreenName("userA"),
  716. },
  717. err: state.ErrDupUser,
  718. },
  719. },
  720. },
  721. },
  722. },
  723. {
  724. name: "invalid AIM screen name",
  725. body: `{"screen_name":"a", "password":"thepassword"}`,
  726. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  727. want: `invalid screen name: screen name must be between 3 and 16 characters`,
  728. statusCode: http.StatusBadRequest,
  729. },
  730. {
  731. name: "invalid AIM password",
  732. body: `{"screen_name":"userA", "password":"1"}`,
  733. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  734. want: `invalid password: invalid password length: password length must be between 4-16 characters`,
  735. statusCode: http.StatusBadRequest,
  736. },
  737. {
  738. name: "invalid ICQ UIN",
  739. body: `{"screen_name":"1000", "password":"thepass"}`,
  740. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  741. want: `invalid uin: uin must be a number in the range 10000-2147483646`,
  742. statusCode: http.StatusBadRequest,
  743. },
  744. {
  745. name: "invalid ICQ password",
  746. body: `{"screen_name":"100003", "password":"thelongpassword"}`,
  747. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  748. want: `invalid password: invalid password length: password must be between 6-8 characters`,
  749. statusCode: http.StatusBadRequest,
  750. },
  751. }
  752. for _, tc := range tt {
  753. t.Run(tc.name, func(t *testing.T) {
  754. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  755. responseRecorder := httptest.NewRecorder()
  756. userManager := newMockUserManager(t)
  757. for _, params := range tc.mockParams.userManagerParams.insertUserParams {
  758. assert.NoError(t, params.u.HashPassword(tc.password))
  759. userManager.EXPECT().
  760. InsertUser(params.u).
  761. Return(params.err)
  762. }
  763. newUUID := func() uuid.UUID { return tc.UUID }
  764. postUserHandler(responseRecorder, request, userManager, newUUID, slog.Default())
  765. if responseRecorder.Code != tc.statusCode {
  766. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  767. }
  768. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  769. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  770. }
  771. })
  772. }
  773. }
  774. func TestUserHandler_DELETE(t *testing.T) {
  775. tt := []struct {
  776. name string
  777. body string
  778. want string
  779. statusCode int
  780. mockParams mockParams
  781. }{
  782. {
  783. name: "with valid user",
  784. body: `{"screen_name":"userA"}`,
  785. want: `User account successfully deleted.`,
  786. statusCode: http.StatusNoContent,
  787. mockParams: mockParams{
  788. userManagerParams: userManagerParams{
  789. deleteUserParams: deleteUserParams{
  790. {
  791. screenName: state.NewIdentScreenName("userA"),
  792. },
  793. },
  794. },
  795. },
  796. },
  797. {
  798. name: "with non-existent user",
  799. body: `{"screen_name":"userA"}`,
  800. want: `user does not exist`,
  801. statusCode: http.StatusNotFound,
  802. mockParams: mockParams{
  803. userManagerParams: userManagerParams{
  804. deleteUserParams: deleteUserParams{
  805. {
  806. screenName: state.NewIdentScreenName("userA"),
  807. err: state.ErrNoUser,
  808. },
  809. },
  810. },
  811. },
  812. },
  813. {
  814. name: "with malformed body",
  815. body: `{"screen_name":"userA"`, // missing closing }
  816. want: `malformed input`,
  817. statusCode: http.StatusBadRequest,
  818. },
  819. {
  820. name: "user handler error",
  821. body: `{"screen_name":"userA"}`,
  822. want: `internal server error`,
  823. statusCode: http.StatusInternalServerError,
  824. mockParams: mockParams{
  825. userManagerParams: userManagerParams{
  826. deleteUserParams: deleteUserParams{
  827. {
  828. screenName: state.NewIdentScreenName("userA"),
  829. err: io.EOF,
  830. },
  831. },
  832. },
  833. },
  834. },
  835. }
  836. for _, tc := range tt {
  837. t.Run(tc.name, func(t *testing.T) {
  838. request := httptest.NewRequest(http.MethodDelete, "/user", strings.NewReader(tc.body))
  839. responseRecorder := httptest.NewRecorder()
  840. userManager := newMockUserManager(t)
  841. for _, params := range tc.mockParams.userManagerParams.deleteUserParams {
  842. userManager.EXPECT().
  843. DeleteUser(params.screenName).
  844. Return(params.err)
  845. }
  846. deleteUserHandler(responseRecorder, request, userManager, slog.Default())
  847. if responseRecorder.Code != tc.statusCode {
  848. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  849. }
  850. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  851. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  852. }
  853. })
  854. }
  855. }
  856. func TestUserPasswordHandler_PUT(t *testing.T) {
  857. tt := []struct {
  858. name string
  859. body string
  860. want string
  861. statusCode int
  862. mockParams mockParams
  863. }{
  864. {
  865. name: "user with valid password",
  866. body: `{"screen_name":"userA", "password":"thenewpassword"}`,
  867. want: `Password successfully reset.`,
  868. statusCode: http.StatusNoContent,
  869. mockParams: mockParams{
  870. userManagerParams: userManagerParams{
  871. setUserPasswordParams: setUserPasswordParams{
  872. {
  873. screenName: state.NewIdentScreenName("userA"),
  874. newPassword: "thenewpassword",
  875. },
  876. },
  877. },
  878. },
  879. },
  880. {
  881. name: "user with invalid password",
  882. body: `{"screen_name":"userA", "password":"a"}`,
  883. want: `invalid password length`,
  884. statusCode: http.StatusBadRequest,
  885. mockParams: mockParams{
  886. userManagerParams: userManagerParams{
  887. setUserPasswordParams: setUserPasswordParams{
  888. {
  889. screenName: state.NewIdentScreenName("userA"),
  890. newPassword: "a",
  891. err: state.ErrPasswordInvalid,
  892. },
  893. },
  894. },
  895. },
  896. },
  897. {
  898. name: "with malformed body",
  899. body: `{"screen_name":"userA", "password":"thepassword"`, // missing closing }
  900. want: `malformed input`,
  901. statusCode: http.StatusBadRequest,
  902. },
  903. {
  904. name: "password updater returns runtime error",
  905. body: `{"screen_name":"userA", "password":"thepassword"}`,
  906. want: `internal server error`,
  907. statusCode: http.StatusInternalServerError,
  908. mockParams: mockParams{
  909. userManagerParams: userManagerParams{
  910. setUserPasswordParams: setUserPasswordParams{
  911. {
  912. screenName: state.NewIdentScreenName("userA"),
  913. newPassword: "thepassword",
  914. err: io.EOF,
  915. },
  916. },
  917. },
  918. },
  919. },
  920. {
  921. name: "user doesn't exist",
  922. body: `{"screen_name":"userA", "password":"thepassword"}`,
  923. want: `user does not exist`,
  924. statusCode: http.StatusNotFound,
  925. mockParams: mockParams{
  926. userManagerParams: userManagerParams{
  927. setUserPasswordParams: setUserPasswordParams{
  928. {
  929. screenName: state.NewIdentScreenName("userA"),
  930. newPassword: "thepassword",
  931. err: state.ErrNoUser,
  932. },
  933. },
  934. },
  935. },
  936. },
  937. }
  938. for _, tc := range tt {
  939. t.Run(tc.name, func(t *testing.T) {
  940. request := httptest.NewRequest(http.MethodPut, "/user", strings.NewReader(tc.body))
  941. responseRecorder := httptest.NewRecorder()
  942. userManager := newMockUserManager(t)
  943. for _, params := range tc.mockParams.userManagerParams.setUserPasswordParams {
  944. userManager.EXPECT().
  945. SetUserPassword(params.screenName, params.newPassword).
  946. Return(params.err)
  947. }
  948. putUserPasswordHandler(responseRecorder, request, userManager, slog.Default())
  949. if responseRecorder.Code != tc.statusCode {
  950. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  951. }
  952. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  953. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  954. }
  955. })
  956. }
  957. }
  958. func TestPublicChatHandler_GET(t *testing.T) {
  959. fnNewSess := func(screenName string) *state.Session {
  960. sess := state.NewSession()
  961. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  962. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  963. return sess
  964. }
  965. chatRoom1 := state.NewChatRoom("chat-room-1-name", state.NewIdentScreenName("chat-room-1-creator"), state.PublicExchange)
  966. chatRoom2 := state.NewChatRoom("chat-room-2-name", state.NewIdentScreenName("chat-room-1-creator"), state.PublicExchange)
  967. tt := []struct {
  968. name string
  969. want string
  970. statusCode int
  971. mockParams mockParams
  972. }{
  973. {
  974. name: "multiple chat rooms with participants",
  975. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","url":"aim:gochat?roomname=chat-room-1-name\u0026exchange=5","participants":[{"id":"usera","screen_name":"userA"},{"id":"userb","screen_name":"userB"}]},{"name":"chat-room-2-name","create_time":"0001-01-01T00:00:00Z","url":"aim:gochat?roomname=chat-room-2-name\u0026exchange=5","participants":[{"id":"userc","screen_name":"userC"},{"id":"userd","screen_name":"userD"}]}]`,
  976. statusCode: http.StatusOK,
  977. mockParams: mockParams{
  978. chatRoomRetrieverParams: chatRoomRetrieverParams{
  979. allChatRoomsParams: allChatRoomsParams{
  980. {
  981. exchange: state.PublicExchange,
  982. result: []state.ChatRoom{
  983. chatRoom1,
  984. chatRoom2,
  985. },
  986. },
  987. },
  988. },
  989. chatSessionRetrieverParams: chatSessionRetrieverParams{
  990. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  991. {
  992. cookie: chatRoom1.Cookie(),
  993. result: []*state.Session{
  994. fnNewSess("userA"),
  995. fnNewSess("userB"),
  996. },
  997. },
  998. {
  999. cookie: chatRoom2.Cookie(),
  1000. result: []*state.Session{
  1001. fnNewSess("userC"),
  1002. fnNewSess("userD"),
  1003. },
  1004. },
  1005. },
  1006. },
  1007. },
  1008. },
  1009. {
  1010. name: "chat room without participants",
  1011. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","url":"aim:gochat?roomname=chat-room-1-name\u0026exchange=5","participants":[]}]`,
  1012. statusCode: http.StatusOK,
  1013. mockParams: mockParams{
  1014. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1015. allChatRoomsParams: allChatRoomsParams{
  1016. {
  1017. exchange: state.PublicExchange,
  1018. result: []state.ChatRoom{
  1019. chatRoom1,
  1020. },
  1021. },
  1022. },
  1023. },
  1024. chatSessionRetrieverParams: chatSessionRetrieverParams{
  1025. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  1026. {
  1027. cookie: chatRoom1.Cookie(),
  1028. result: []*state.Session{},
  1029. },
  1030. },
  1031. },
  1032. },
  1033. },
  1034. {
  1035. name: "no chat rooms",
  1036. want: `[]`,
  1037. statusCode: http.StatusOK,
  1038. mockParams: mockParams{
  1039. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1040. allChatRoomsParams: allChatRoomsParams{
  1041. {
  1042. exchange: state.PublicExchange,
  1043. result: []state.ChatRoom{},
  1044. },
  1045. },
  1046. },
  1047. },
  1048. },
  1049. }
  1050. for _, tc := range tt {
  1051. t.Run(tc.name, func(t *testing.T) {
  1052. request := httptest.NewRequest(http.MethodGet, "/chat/room/public", nil)
  1053. responseRecorder := httptest.NewRecorder()
  1054. chatRoomRetriever := newMockChatRoomRetriever(t)
  1055. for _, params := range tc.mockParams.chatRoomRetrieverParams.allChatRoomsParams {
  1056. chatRoomRetriever.EXPECT().
  1057. AllChatRooms(params.exchange).
  1058. Return(params.result, params.err)
  1059. }
  1060. chatSessionRetriever := newMockChatSessionRetriever(t)
  1061. for _, params := range tc.mockParams.chatSessionRetrieverParams.chatSessionRetrieverAllSessionsParams {
  1062. chatSessionRetriever.EXPECT().
  1063. AllSessions(params.cookie).
  1064. Return(params.result)
  1065. }
  1066. getPublicChatHandler(responseRecorder, request, chatRoomRetriever, chatSessionRetriever, slog.Default())
  1067. if responseRecorder.Code != tc.statusCode {
  1068. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1069. }
  1070. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1071. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  1072. }
  1073. })
  1074. }
  1075. }
  1076. func TestPrivateChatHandler_GET(t *testing.T) {
  1077. fnNewSess := func(screenName string) *state.Session {
  1078. sess := state.NewSession()
  1079. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  1080. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  1081. return sess
  1082. }
  1083. chatRoom1 := state.NewChatRoom("chat-room-1-name", state.NewIdentScreenName("chat-room-1-creator"), state.PrivateExchange)
  1084. chatRoom2 := state.NewChatRoom("chat-room-2-name", state.NewIdentScreenName("chat-room-2-creator"), state.PrivateExchange)
  1085. tt := []struct {
  1086. name string
  1087. want string
  1088. statusCode int
  1089. mockParams mockParams
  1090. }{
  1091. {
  1092. name: "multiple chat rooms with participants",
  1093. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","creator_id":"chat-room-1-creator","url":"aim:gochat?roomname=chat-room-1-name\u0026exchange=4","participants":[{"id":"usera","screen_name":"userA"},{"id":"userb","screen_name":"userB"}]},{"name":"chat-room-2-name","create_time":"0001-01-01T00:00:00Z","creator_id":"chat-room-2-creator","url":"aim:gochat?roomname=chat-room-2-name\u0026exchange=4","participants":[{"id":"userc","screen_name":"userC"},{"id":"userd","screen_name":"userD"}]}]`,
  1094. statusCode: http.StatusOK,
  1095. mockParams: mockParams{
  1096. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1097. allChatRoomsParams: allChatRoomsParams{
  1098. {
  1099. exchange: state.PrivateExchange,
  1100. result: []state.ChatRoom{
  1101. chatRoom1,
  1102. chatRoom2,
  1103. },
  1104. },
  1105. },
  1106. },
  1107. chatSessionRetrieverParams: chatSessionRetrieverParams{
  1108. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  1109. {
  1110. cookie: chatRoom1.Cookie(),
  1111. result: []*state.Session{
  1112. fnNewSess("userA"),
  1113. fnNewSess("userB"),
  1114. },
  1115. },
  1116. {
  1117. cookie: chatRoom2.Cookie(),
  1118. result: []*state.Session{
  1119. fnNewSess("userC"),
  1120. fnNewSess("userD"),
  1121. },
  1122. },
  1123. },
  1124. },
  1125. },
  1126. },
  1127. {
  1128. name: "chat room without participants",
  1129. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","creator_id":"chat-room-1-creator","url":"aim:gochat?roomname=chat-room-1-name\u0026exchange=4","participants":[]}]`,
  1130. statusCode: http.StatusOK,
  1131. mockParams: mockParams{
  1132. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1133. allChatRoomsParams: allChatRoomsParams{
  1134. {
  1135. exchange: state.PrivateExchange,
  1136. result: []state.ChatRoom{
  1137. chatRoom1,
  1138. },
  1139. },
  1140. },
  1141. },
  1142. chatSessionRetrieverParams: chatSessionRetrieverParams{
  1143. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  1144. {
  1145. cookie: chatRoom1.Cookie(),
  1146. result: []*state.Session{},
  1147. },
  1148. },
  1149. },
  1150. },
  1151. },
  1152. {
  1153. name: "no chat rooms",
  1154. want: `[]`,
  1155. statusCode: http.StatusOK,
  1156. mockParams: mockParams{
  1157. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1158. allChatRoomsParams: allChatRoomsParams{
  1159. {
  1160. exchange: state.PrivateExchange,
  1161. result: []state.ChatRoom{},
  1162. },
  1163. },
  1164. },
  1165. },
  1166. },
  1167. }
  1168. for _, tc := range tt {
  1169. t.Run(tc.name, func(t *testing.T) {
  1170. request := httptest.NewRequest(http.MethodGet, "/chat/room/private", nil)
  1171. responseRecorder := httptest.NewRecorder()
  1172. chatRoomRetriever := newMockChatRoomRetriever(t)
  1173. for _, params := range tc.mockParams.chatRoomRetrieverParams.allChatRoomsParams {
  1174. chatRoomRetriever.EXPECT().
  1175. AllChatRooms(params.exchange).
  1176. Return(params.result, params.err)
  1177. }
  1178. chatSessionRetriever := newMockChatSessionRetriever(t)
  1179. for _, params := range tc.mockParams.chatSessionRetrieverParams.chatSessionRetrieverAllSessionsParams {
  1180. chatSessionRetriever.EXPECT().
  1181. AllSessions(params.cookie).
  1182. Return(params.result)
  1183. }
  1184. getPrivateChatHandler(responseRecorder, request, chatRoomRetriever, chatSessionRetriever, slog.Default())
  1185. if responseRecorder.Code != tc.statusCode {
  1186. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1187. }
  1188. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1189. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  1190. }
  1191. })
  1192. }
  1193. }
  1194. func TestInstantMessageHandler_POST(t *testing.T) {
  1195. type relayToScreenNameInputs struct {
  1196. sender state.IdentScreenName
  1197. recipient state.IdentScreenName
  1198. msg string
  1199. }
  1200. tt := []struct {
  1201. name string
  1202. relayToScreenNameInputs []relayToScreenNameInputs
  1203. body string
  1204. want string
  1205. statusCode int
  1206. }{
  1207. {
  1208. name: "send an instant message",
  1209. relayToScreenNameInputs: []relayToScreenNameInputs{
  1210. {
  1211. sender: state.NewIdentScreenName("sender_sn"),
  1212. recipient: state.NewIdentScreenName("recip_sn"),
  1213. msg: "hello world!",
  1214. },
  1215. },
  1216. body: `{"from":"sender_sn","to":"recip_sn","text":"hello world!"}`,
  1217. want: `Message sent successfully.`,
  1218. statusCode: http.StatusOK,
  1219. },
  1220. {
  1221. name: "with malformed body",
  1222. body: `{"screen_name":"userA", "password":"thepassword"`,
  1223. want: `malformed input`,
  1224. statusCode: http.StatusBadRequest,
  1225. },
  1226. }
  1227. for _, tc := range tt {
  1228. t.Run(tc.name, func(t *testing.T) {
  1229. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  1230. responseRecorder := httptest.NewRecorder()
  1231. messageRelayer := newMockMessageRelayer(t)
  1232. for _, params := range tc.relayToScreenNameInputs {
  1233. validateSNAC := func(msg wire.SNACMessage) bool {
  1234. body := msg.Body.(wire.SNAC_0x04_0x07_ICBMChannelMsgToClient)
  1235. assert.Equal(t, params.sender.String(), body.TLVUserInfo.ScreenName)
  1236. b, ok := body.Slice(wire.ICBMTLVAOLIMData)
  1237. assert.True(t, ok)
  1238. txt, err := wire.UnmarshalICBMMessageText(b)
  1239. assert.NoError(t, err)
  1240. assert.Equal(t, params.msg, txt)
  1241. return true
  1242. }
  1243. messageRelayer.EXPECT().
  1244. RelayToScreenName(mock.Anything, params.recipient, mock.MatchedBy(validateSNAC))
  1245. }
  1246. postInstantMessageHandler(responseRecorder, request, messageRelayer, slog.Default())
  1247. if responseRecorder.Code != tc.statusCode {
  1248. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1249. }
  1250. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1251. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  1252. }
  1253. })
  1254. }
  1255. }
  1256. func TestVersionHandler_GET(t *testing.T) {
  1257. tt := []struct {
  1258. name string
  1259. want string
  1260. statusCode int
  1261. buildInfo config.Build
  1262. }{
  1263. {
  1264. name: "get ras version",
  1265. want: `{"version":"13.3.7","commit":"asdfASDF12345678","date":"2024-03-01"}`,
  1266. statusCode: http.StatusOK,
  1267. buildInfo: config.Build{
  1268. Version: "13.3.7",
  1269. Commit: "asdfASDF12345678",
  1270. Date: "2024-03-01",
  1271. },
  1272. },
  1273. }
  1274. for _, tc := range tt {
  1275. t.Run(tc.name, func(t *testing.T) {
  1276. responseRecorder := httptest.NewRecorder()
  1277. getVersionHandler(responseRecorder, tc.buildInfo)
  1278. if responseRecorder.Code != tc.statusCode {
  1279. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1280. }
  1281. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1282. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  1283. }
  1284. })
  1285. }
  1286. }