mgmt_api_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. package http
  2. import (
  3. "io"
  4. "log/slog"
  5. "net/http"
  6. "net/http/httptest"
  7. "strings"
  8. "testing"
  9. "github.com/google/uuid"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/mock"
  12. "github.com/mk6i/retro-aim-server/state"
  13. "github.com/mk6i/retro-aim-server/wire"
  14. )
  15. func TestSessionHandler_GET(t *testing.T) {
  16. fnNewSess := func(screenName string) *state.Session {
  17. sess := state.NewSession()
  18. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  19. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  20. return sess
  21. }
  22. tt := []struct {
  23. name string
  24. sessions []*state.Session
  25. userHandlerErr error
  26. want string
  27. statusCode int
  28. }{
  29. {
  30. name: "without sessions",
  31. sessions: []*state.Session{},
  32. want: `{"count":0,"sessions":[]}`,
  33. statusCode: http.StatusOK,
  34. },
  35. {
  36. name: "with sessions",
  37. sessions: []*state.Session{
  38. fnNewSess("userA"),
  39. fnNewSess("userB"),
  40. },
  41. want: `{"count":2,"sessions":[{"id":"usera","screen_name":"userA"},{"id":"userb","screen_name":"userB"}]}`,
  42. statusCode: http.StatusOK,
  43. },
  44. }
  45. for _, tc := range tt {
  46. t.Run(tc.name, func(t *testing.T) {
  47. request := httptest.NewRequest(http.MethodGet, "/session", nil)
  48. responseRecorder := httptest.NewRecorder()
  49. sessionRetriever := newMockSessionRetriever(t)
  50. sessionRetriever.EXPECT().
  51. AllSessions().
  52. Return(tc.sessions)
  53. sessionHandler(responseRecorder, request, sessionRetriever)
  54. if responseRecorder.Code != tc.statusCode {
  55. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  56. }
  57. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  58. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  59. }
  60. })
  61. }
  62. }
  63. func TestSessionHandler_DisallowedMethod(t *testing.T) {
  64. request := httptest.NewRequest(http.MethodPut, "/session", nil)
  65. responseRecorder := httptest.NewRecorder()
  66. sessionHandler(responseRecorder, request, nil)
  67. wantCode := http.StatusMethodNotAllowed
  68. if responseRecorder.Code != wantCode {
  69. t.Errorf("want status '%d', got '%d'", http.StatusMethodNotAllowed, responseRecorder.Code)
  70. }
  71. wantBody := `method not allowed`
  72. if strings.TrimSpace(responseRecorder.Body.String()) != wantBody {
  73. t.Errorf("want '%s', got '%s'", wantBody, responseRecorder.Body)
  74. }
  75. }
  76. func TestUserHandler_GET(t *testing.T) {
  77. tt := []struct {
  78. name string
  79. users []state.User
  80. userHandlerErr error
  81. want string
  82. statusCode int
  83. }{
  84. {
  85. name: "empty user store",
  86. users: []state.User{},
  87. want: `[]`,
  88. statusCode: http.StatusOK,
  89. },
  90. {
  91. name: "user store containing 2 users",
  92. users: []state.User{
  93. {
  94. DisplayScreenName: "userA",
  95. IdentScreenName: state.NewIdentScreenName("userA"),
  96. },
  97. {
  98. DisplayScreenName: "userB",
  99. IdentScreenName: state.NewIdentScreenName("userB"),
  100. },
  101. },
  102. want: `[{"id":"usera","screen_name":"userA"},{"id":"userb","screen_name":"userB"}]`,
  103. statusCode: http.StatusOK,
  104. },
  105. {
  106. name: "user handler error",
  107. users: []state.User{},
  108. userHandlerErr: io.EOF,
  109. want: `internal server error`,
  110. statusCode: http.StatusInternalServerError,
  111. },
  112. }
  113. for _, tc := range tt {
  114. t.Run(tc.name, func(t *testing.T) {
  115. request := httptest.NewRequest(http.MethodGet, "/user", nil)
  116. responseRecorder := httptest.NewRecorder()
  117. userManager := newMockUserManager(t)
  118. userManager.EXPECT().
  119. AllUsers().
  120. Return(tc.users, tc.userHandlerErr)
  121. userHandler(responseRecorder, request, userManager, nil, slog.Default())
  122. if responseRecorder.Code != tc.statusCode {
  123. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  124. }
  125. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  126. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  127. }
  128. })
  129. }
  130. }
  131. func TestUserHandler_POST(t *testing.T) {
  132. tt := []struct {
  133. name string
  134. body string
  135. UUID uuid.UUID
  136. user state.User
  137. userHandlerErr error
  138. want string
  139. statusCode int
  140. }{
  141. {
  142. name: "with valid user",
  143. body: `{"screen_name":"userA", "password":"thepassword"}`,
  144. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  145. user: func() state.User {
  146. user := state.User{
  147. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  148. DisplayScreenName: "userA",
  149. IdentScreenName: state.NewIdentScreenName("userA"),
  150. }
  151. assert.NoError(t, user.HashPassword("thepassword"))
  152. return user
  153. }(),
  154. want: `User account created successfully.`,
  155. statusCode: http.StatusCreated,
  156. },
  157. {
  158. name: "with malformed body",
  159. body: `{"screen_name":"userA", "password":"thepassword"`,
  160. user: state.User{},
  161. want: `malformed input`,
  162. statusCode: http.StatusBadRequest,
  163. },
  164. {
  165. name: "user handler error",
  166. body: `{"screen_name":"userA", "password":"thepassword"}`,
  167. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  168. user: func() state.User {
  169. user := state.User{
  170. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  171. DisplayScreenName: "userA",
  172. IdentScreenName: state.NewIdentScreenName("userA"),
  173. }
  174. assert.NoError(t, user.HashPassword("thepassword"))
  175. return user
  176. }(),
  177. userHandlerErr: io.EOF,
  178. want: `internal server error`,
  179. statusCode: http.StatusInternalServerError,
  180. },
  181. {
  182. name: "duplicate user",
  183. body: `{"screen_name":"userA", "password":"thepassword"}`,
  184. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  185. user: func() state.User {
  186. user := state.User{
  187. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  188. DisplayScreenName: "userA",
  189. IdentScreenName: state.NewIdentScreenName("userA"),
  190. }
  191. assert.NoError(t, user.HashPassword("thepassword"))
  192. return user
  193. }(),
  194. userHandlerErr: state.ErrDupUser,
  195. want: `user already exists`,
  196. statusCode: http.StatusConflict,
  197. },
  198. }
  199. for _, tc := range tt {
  200. t.Run(tc.name, func(t *testing.T) {
  201. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  202. responseRecorder := httptest.NewRecorder()
  203. userManager := newMockUserManager(t)
  204. if tc.user.IdentScreenName.String() != "" {
  205. userManager.EXPECT().
  206. InsertUser(tc.user).
  207. Return(tc.userHandlerErr)
  208. }
  209. newUUID := func() uuid.UUID { return tc.UUID }
  210. userHandler(responseRecorder, request, userManager, newUUID, slog.Default())
  211. if responseRecorder.Code != tc.statusCode {
  212. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  213. }
  214. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  215. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  216. }
  217. })
  218. }
  219. }
  220. func TestUserHandler_DELETE(t *testing.T) {
  221. tt := []struct {
  222. name string
  223. body string
  224. user state.User
  225. userHandlerErr error
  226. want string
  227. statusCode int
  228. }{
  229. {
  230. name: "with valid user",
  231. body: `{"screen_name":"userA"}`,
  232. user: state.User{
  233. IdentScreenName: state.NewIdentScreenName("userA"),
  234. },
  235. want: `User account successfully deleted.`,
  236. statusCode: http.StatusNoContent,
  237. },
  238. {
  239. name: "with non-existent user",
  240. body: `{"screen_name":"userA"}`,
  241. user: state.User{
  242. IdentScreenName: state.NewIdentScreenName("userA"),
  243. },
  244. userHandlerErr: state.ErrNoUser,
  245. want: `user does not exist`,
  246. statusCode: http.StatusNotFound,
  247. },
  248. {
  249. name: "with malformed body",
  250. body: `{"screen_name":"userA"`,
  251. user: state.User{},
  252. want: `malformed input`,
  253. statusCode: http.StatusBadRequest,
  254. },
  255. {
  256. name: "user handler error",
  257. body: `{"screen_name":"userA"}`,
  258. user: state.User{
  259. IdentScreenName: state.NewIdentScreenName("userA"),
  260. },
  261. userHandlerErr: io.EOF,
  262. want: `internal server error`,
  263. statusCode: http.StatusInternalServerError,
  264. },
  265. }
  266. for _, tc := range tt {
  267. t.Run(tc.name, func(t *testing.T) {
  268. request := httptest.NewRequest(http.MethodDelete, "/user", strings.NewReader(tc.body))
  269. responseRecorder := httptest.NewRecorder()
  270. userManager := newMockUserManager(t)
  271. if tc.user.IdentScreenName.String() != "" {
  272. userManager.EXPECT().
  273. DeleteUser(tc.user.IdentScreenName).
  274. Return(tc.userHandlerErr)
  275. }
  276. userHandler(responseRecorder, request, userManager, nil, slog.Default())
  277. if responseRecorder.Code != tc.statusCode {
  278. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  279. }
  280. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  281. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  282. }
  283. })
  284. }
  285. }
  286. func TestUserPasswordHandler_PUT(t *testing.T) {
  287. tt := []struct {
  288. name string
  289. body string
  290. user state.User
  291. UUID uuid.UUID
  292. userHandlerErr error
  293. want string
  294. statusCode int
  295. }{
  296. {
  297. name: "with valid password",
  298. body: `{"screen_name":"userA", "password":"thepassword"}`,
  299. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  300. user: func() state.User {
  301. user := state.User{
  302. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  303. IdentScreenName: state.NewIdentScreenName("userA"),
  304. }
  305. assert.NoError(t, user.HashPassword("thepassword"))
  306. return user
  307. }(),
  308. want: ``,
  309. statusCode: http.StatusNoContent,
  310. },
  311. {
  312. name: "with malformed body",
  313. body: `{"screen_name":"userA", "password":"thepassword"`,
  314. user: state.User{},
  315. want: `malformed input`,
  316. statusCode: http.StatusBadRequest,
  317. },
  318. {
  319. name: "user password handler error",
  320. body: `{"screen_name":"userA", "password":"thepassword"}`,
  321. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  322. user: func() state.User {
  323. user := state.User{
  324. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  325. IdentScreenName: state.NewIdentScreenName("userA"),
  326. }
  327. assert.NoError(t, user.HashPassword("thepassword"))
  328. return user
  329. }(),
  330. userHandlerErr: io.EOF,
  331. want: `internal server error`,
  332. statusCode: http.StatusInternalServerError,
  333. },
  334. {
  335. name: "user doesn't exist",
  336. body: `{"screen_name":"userA", "password":"thepassword"}`,
  337. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  338. user: func() state.User {
  339. user := state.User{
  340. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  341. IdentScreenName: state.NewIdentScreenName("userA"),
  342. }
  343. assert.NoError(t, user.HashPassword("thepassword"))
  344. return user
  345. }(),
  346. userHandlerErr: state.ErrNoUser,
  347. want: `user does not exist`,
  348. statusCode: http.StatusNotFound,
  349. },
  350. }
  351. for _, tc := range tt {
  352. t.Run(tc.name, func(t *testing.T) {
  353. request := httptest.NewRequest(http.MethodPut, "/user", strings.NewReader(tc.body))
  354. responseRecorder := httptest.NewRecorder()
  355. userManager := newMockUserManager(t)
  356. if tc.user.IdentScreenName.String() != "" {
  357. userManager.EXPECT().
  358. SetUserPassword(tc.user).
  359. Return(tc.userHandlerErr)
  360. }
  361. newUUID := func() uuid.UUID { return tc.UUID }
  362. userPasswordHandler(responseRecorder, request, userManager, newUUID, slog.Default())
  363. if responseRecorder.Code != tc.statusCode {
  364. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  365. }
  366. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  367. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  368. }
  369. })
  370. }
  371. }
  372. func TestUserHandler_DisallowedMethod(t *testing.T) {
  373. request := httptest.NewRequest(http.MethodPut, "/user", nil)
  374. responseRecorder := httptest.NewRecorder()
  375. userHandler(responseRecorder, request, nil, nil, nil)
  376. wantCode := http.StatusMethodNotAllowed
  377. if responseRecorder.Code != wantCode {
  378. t.Errorf("want status '%d', got '%d'", http.StatusMethodNotAllowed, responseRecorder.Code)
  379. }
  380. wantBody := `method not allowed`
  381. if strings.TrimSpace(responseRecorder.Body.String()) != wantBody {
  382. t.Errorf("want '%s', got '%s'", wantBody, responseRecorder.Body)
  383. }
  384. }
  385. func TestPublicChatHandler_GET(t *testing.T) {
  386. fnNewSess := func(screenName string) *state.Session {
  387. sess := state.NewSession()
  388. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  389. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  390. return sess
  391. }
  392. type allChatRoomsParams struct {
  393. exchange uint16
  394. result []state.ChatRoom
  395. err error
  396. }
  397. type allSessionsParams struct {
  398. cookie string
  399. result []*state.Session
  400. }
  401. chatRoom1 := state.NewChatRoom("chat-room-1-name", state.NewIdentScreenName("chat-room-1-creator"), state.PublicExchange)
  402. chatRoom2 := state.NewChatRoom("chat-room-2-name", state.NewIdentScreenName("chat-room-1-creator"), state.PublicExchange)
  403. tt := []struct {
  404. name string
  405. allChatRoomsParams allChatRoomsParams
  406. allSessionsParams []allSessionsParams
  407. userHandlerErr error
  408. want string
  409. statusCode int
  410. }{
  411. {
  412. name: "multiple chat rooms with participants",
  413. allChatRoomsParams: allChatRoomsParams{
  414. exchange: state.PublicExchange,
  415. result: []state.ChatRoom{
  416. chatRoom1,
  417. chatRoom2,
  418. },
  419. },
  420. allSessionsParams: []allSessionsParams{
  421. {
  422. cookie: chatRoom1.Cookie(),
  423. result: []*state.Session{
  424. fnNewSess("userA"),
  425. fnNewSess("userB"),
  426. },
  427. },
  428. {
  429. cookie: chatRoom2.Cookie(),
  430. result: []*state.Session{
  431. fnNewSess("userC"),
  432. fnNewSess("userD"),
  433. },
  434. },
  435. },
  436. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","url":"aim:gochat?exchange=5\u0026roomname=chat-room-1-name","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?exchange=5\u0026roomname=chat-room-2-name","participants":[{"id":"userc","screen_name":"userC"},{"id":"userd","screen_name":"userD"}]}]`,
  437. statusCode: http.StatusOK,
  438. },
  439. {
  440. name: "chat room without participants",
  441. allChatRoomsParams: allChatRoomsParams{
  442. exchange: state.PublicExchange,
  443. result: []state.ChatRoom{
  444. chatRoom1,
  445. },
  446. },
  447. allSessionsParams: []allSessionsParams{
  448. {
  449. cookie: chatRoom1.Cookie(),
  450. result: []*state.Session{},
  451. },
  452. },
  453. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","url":"aim:gochat?exchange=5\u0026roomname=chat-room-1-name","participants":[]}]`,
  454. statusCode: http.StatusOK,
  455. },
  456. {
  457. name: "no chat rooms",
  458. allChatRoomsParams: allChatRoomsParams{
  459. exchange: state.PublicExchange,
  460. result: []state.ChatRoom{},
  461. },
  462. allSessionsParams: []allSessionsParams{},
  463. want: `[]`,
  464. statusCode: http.StatusOK,
  465. },
  466. }
  467. for _, tc := range tt {
  468. t.Run(tc.name, func(t *testing.T) {
  469. request := httptest.NewRequest(http.MethodGet, "/chat/room/public", nil)
  470. responseRecorder := httptest.NewRecorder()
  471. chatRoomRetriever := newMockChatRoomRetriever(t)
  472. chatRoomRetriever.EXPECT().
  473. AllChatRooms(tc.allChatRoomsParams.exchange).
  474. Return(tc.allChatRoomsParams.result, tc.allChatRoomsParams.err)
  475. chatSessionRetriever := newMockChatSessionRetriever(t)
  476. for _, params := range tc.allSessionsParams {
  477. chatSessionRetriever.EXPECT().
  478. AllSessions(params.cookie).
  479. Return(params.result)
  480. }
  481. getPublicChatHandler(responseRecorder, request, chatRoomRetriever, chatSessionRetriever, slog.Default())
  482. if responseRecorder.Code != tc.statusCode {
  483. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  484. }
  485. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  486. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  487. }
  488. })
  489. }
  490. }
  491. func TestPrivateChatHandler_GET(t *testing.T) {
  492. fnNewSess := func(screenName string) *state.Session {
  493. sess := state.NewSession()
  494. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  495. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  496. return sess
  497. }
  498. type allChatRoomsParams struct {
  499. exchange uint16
  500. result []state.ChatRoom
  501. err error
  502. }
  503. type allSessionsParams struct {
  504. cookie string
  505. result []*state.Session
  506. }
  507. chatRoom1 := state.NewChatRoom("chat-room-1-name", state.NewIdentScreenName("chat-room-1-creator"), state.PrivateExchange)
  508. chatRoom2 := state.NewChatRoom("chat-room-2-name", state.NewIdentScreenName("chat-room-2-creator"), state.PrivateExchange)
  509. tt := []struct {
  510. name string
  511. allChatRoomsParams allChatRoomsParams
  512. allSessionsParams []allSessionsParams
  513. userHandlerErr error
  514. want string
  515. statusCode int
  516. }{
  517. {
  518. name: "multiple chat rooms with participants",
  519. allChatRoomsParams: allChatRoomsParams{
  520. exchange: state.PrivateExchange,
  521. result: []state.ChatRoom{
  522. chatRoom1,
  523. chatRoom2,
  524. },
  525. },
  526. allSessionsParams: []allSessionsParams{
  527. {
  528. cookie: chatRoom1.Cookie(),
  529. result: []*state.Session{
  530. fnNewSess("userA"),
  531. fnNewSess("userB"),
  532. },
  533. },
  534. {
  535. cookie: chatRoom2.Cookie(),
  536. result: []*state.Session{
  537. fnNewSess("userC"),
  538. fnNewSess("userD"),
  539. },
  540. },
  541. },
  542. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","creator_id":"chat-room-1-creator","url":"aim:gochat?exchange=4\u0026roomname=chat-room-1-name","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?exchange=4\u0026roomname=chat-room-2-name","participants":[{"id":"userc","screen_name":"userC"},{"id":"userd","screen_name":"userD"}]}]`,
  543. statusCode: http.StatusOK,
  544. },
  545. {
  546. name: "chat room without participants",
  547. allChatRoomsParams: allChatRoomsParams{
  548. exchange: state.PrivateExchange,
  549. result: []state.ChatRoom{
  550. chatRoom1,
  551. },
  552. },
  553. allSessionsParams: []allSessionsParams{
  554. {
  555. cookie: chatRoom1.Cookie(),
  556. result: []*state.Session{},
  557. },
  558. },
  559. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","creator_id":"chat-room-1-creator","url":"aim:gochat?exchange=4\u0026roomname=chat-room-1-name","participants":[]}]`,
  560. statusCode: http.StatusOK,
  561. },
  562. {
  563. name: "no chat rooms",
  564. allChatRoomsParams: allChatRoomsParams{
  565. exchange: state.PrivateExchange,
  566. result: []state.ChatRoom{},
  567. },
  568. allSessionsParams: []allSessionsParams{},
  569. want: `[]`,
  570. statusCode: http.StatusOK,
  571. },
  572. }
  573. for _, tc := range tt {
  574. t.Run(tc.name, func(t *testing.T) {
  575. request := httptest.NewRequest(http.MethodGet, "/chat/room/private", nil)
  576. responseRecorder := httptest.NewRecorder()
  577. chatRoomRetriever := newMockChatRoomRetriever(t)
  578. chatRoomRetriever.EXPECT().
  579. AllChatRooms(tc.allChatRoomsParams.exchange).
  580. Return(tc.allChatRoomsParams.result, tc.allChatRoomsParams.err)
  581. chatSessionRetriever := newMockChatSessionRetriever(t)
  582. for _, params := range tc.allSessionsParams {
  583. chatSessionRetriever.EXPECT().
  584. AllSessions(params.cookie).
  585. Return(params.result)
  586. }
  587. getPrivateChatHandler(responseRecorder, request, chatRoomRetriever, chatSessionRetriever, slog.Default())
  588. if responseRecorder.Code != tc.statusCode {
  589. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  590. }
  591. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  592. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  593. }
  594. })
  595. }
  596. }
  597. func TestInstantMessageHandler_POST(t *testing.T) {
  598. type relayToScreenNameParams struct {
  599. sender state.IdentScreenName
  600. recipient state.IdentScreenName
  601. msg string
  602. }
  603. tt := []struct {
  604. name string
  605. relayToScreenNameParams []relayToScreenNameParams
  606. body string
  607. want string
  608. statusCode int
  609. }{
  610. {
  611. name: "send an instant message",
  612. relayToScreenNameParams: []relayToScreenNameParams{
  613. {
  614. sender: state.NewIdentScreenName("sender_sn"),
  615. recipient: state.NewIdentScreenName("recip_sn"),
  616. msg: "hello world!",
  617. },
  618. },
  619. body: `{"from":"sender_sn","to":"recip_sn","text":"hello world!"}`,
  620. want: `Message sent successfully.`,
  621. statusCode: http.StatusOK,
  622. },
  623. {
  624. name: "with malformed body",
  625. body: `{"screen_name":"userA", "password":"thepassword"`,
  626. want: `malformed input`,
  627. statusCode: http.StatusBadRequest,
  628. },
  629. }
  630. for _, tc := range tt {
  631. t.Run(tc.name, func(t *testing.T) {
  632. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  633. responseRecorder := httptest.NewRecorder()
  634. messageRelayer := newMockMessageRelayer(t)
  635. for _, params := range tc.relayToScreenNameParams {
  636. validateSNAC := func(msg wire.SNACMessage) bool {
  637. body := msg.Body.(wire.SNAC_0x04_0x07_ICBMChannelMsgToClient)
  638. assert.Equal(t, params.sender.String(), body.TLVUserInfo.ScreenName)
  639. b, ok := body.Slice(wire.ICBMTLVAOLIMData)
  640. assert.True(t, ok)
  641. txt, err := wire.UnmarshalICBMMessageText(b)
  642. assert.NoError(t, err)
  643. assert.Equal(t, params.msg, txt)
  644. return true
  645. }
  646. messageRelayer.EXPECT().
  647. RelayToScreenName(mock.Anything, params.recipient, mock.MatchedBy(validateSNAC))
  648. }
  649. postInstantMessageHandler(responseRecorder, request, messageRelayer, slog.Default())
  650. if responseRecorder.Code != tc.statusCode {
  651. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  652. }
  653. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  654. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  655. }
  656. })
  657. }
  658. }