mgmt_api_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. package http
  2. import (
  3. "io"
  4. "log/slog"
  5. "net/http"
  6. "net/http/httptest"
  7. "strings"
  8. "testing"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/mk6i/retro-aim-server/state"
  11. )
  12. func TestSessionHandler_GET(t *testing.T) {
  13. fnNewSess := func(screenName string) *state.Session {
  14. sess := state.NewSession()
  15. sess.SetScreenName(screenName)
  16. return sess
  17. }
  18. tt := []struct {
  19. name string
  20. sessions []*state.Session
  21. userHandlerErr error
  22. want string
  23. statusCode int
  24. }{
  25. {
  26. name: "without sessions",
  27. sessions: []*state.Session{},
  28. want: `{"count":0,"sessions":[]}`,
  29. statusCode: http.StatusOK,
  30. },
  31. {
  32. name: "with sessions",
  33. sessions: []*state.Session{
  34. fnNewSess("userA"),
  35. fnNewSess("userB"),
  36. },
  37. want: `{"count":2,"sessions":[{"screen_name":"userA"},{"screen_name":"userB"}]}`,
  38. statusCode: http.StatusOK,
  39. },
  40. }
  41. for _, tc := range tt {
  42. t.Run(tc.name, func(t *testing.T) {
  43. request := httptest.NewRequest(http.MethodGet, "/session", nil)
  44. responseRecorder := httptest.NewRecorder()
  45. sessionRetriever := newMockSessionRetriever(t)
  46. sessionRetriever.EXPECT().
  47. AllSessions().
  48. Return(tc.sessions)
  49. sessionHandler(responseRecorder, request, sessionRetriever)
  50. if responseRecorder.Code != tc.statusCode {
  51. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  52. }
  53. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  54. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  55. }
  56. })
  57. }
  58. }
  59. func TestSessionHandler_DisallowedMethod(t *testing.T) {
  60. request := httptest.NewRequest(http.MethodPut, "/session", nil)
  61. responseRecorder := httptest.NewRecorder()
  62. sessionHandler(responseRecorder, request, nil)
  63. wantCode := http.StatusMethodNotAllowed
  64. if responseRecorder.Code != wantCode {
  65. t.Errorf("want status '%d', got '%d'", http.StatusMethodNotAllowed, responseRecorder.Code)
  66. }
  67. wantBody := `method not allowed`
  68. if strings.TrimSpace(responseRecorder.Body.String()) != wantBody {
  69. t.Errorf("want '%s', got '%s'", wantBody, responseRecorder.Body)
  70. }
  71. }
  72. func TestUserHandler_GET(t *testing.T) {
  73. tt := []struct {
  74. name string
  75. users []state.User
  76. userHandlerErr error
  77. want string
  78. statusCode int
  79. }{
  80. {
  81. name: "empty user store",
  82. users: []state.User{},
  83. want: `[]`,
  84. statusCode: http.StatusOK,
  85. },
  86. {
  87. name: "user store containing 2 users",
  88. users: []state.User{
  89. {ScreenName: "userA"},
  90. {ScreenName: "userB"},
  91. },
  92. want: `[{"screen_name":"userA"},{"screen_name":"userB"}]`,
  93. statusCode: http.StatusOK,
  94. },
  95. {
  96. name: "user handler error",
  97. users: []state.User{},
  98. userHandlerErr: io.EOF,
  99. want: `internal server error`,
  100. statusCode: http.StatusInternalServerError,
  101. },
  102. }
  103. for _, tc := range tt {
  104. t.Run(tc.name, func(t *testing.T) {
  105. request := httptest.NewRequest(http.MethodGet, "/user", nil)
  106. responseRecorder := httptest.NewRecorder()
  107. userManager := newMockUserManager(t)
  108. userManager.EXPECT().
  109. AllUsers().
  110. Return(tc.users, tc.userHandlerErr)
  111. userHandler(responseRecorder, request, userManager, nil, slog.Default())
  112. if responseRecorder.Code != tc.statusCode {
  113. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  114. }
  115. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  116. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  117. }
  118. })
  119. }
  120. }
  121. func TestUserHandler_POST(t *testing.T) {
  122. tt := []struct {
  123. name string
  124. body string
  125. user state.User
  126. userHandlerErr error
  127. want string
  128. statusCode int
  129. }{
  130. {
  131. name: "with valid user",
  132. body: `{"screen_name":"userA", "password":"thepassword"}`,
  133. user: func() state.User {
  134. user := state.User{
  135. AuthKey: "theAuthKey",
  136. ScreenName: "userA",
  137. }
  138. assert.NoError(t, user.HashPassword("thepassword"))
  139. return user
  140. }(),
  141. want: `User account created successfully.`,
  142. statusCode: http.StatusCreated,
  143. },
  144. {
  145. name: "with malformed body",
  146. body: `{"screen_name":"userA", "password":"thepassword"`,
  147. user: state.User{},
  148. want: `malformed input`,
  149. statusCode: http.StatusBadRequest,
  150. },
  151. {
  152. name: "user handler error",
  153. body: `{"screen_name":"userA", "password":"thepassword"}`,
  154. user: func() state.User {
  155. user := state.User{
  156. AuthKey: "theAuthKey",
  157. ScreenName: "userA",
  158. }
  159. assert.NoError(t, user.HashPassword("thepassword"))
  160. return user
  161. }(),
  162. userHandlerErr: io.EOF,
  163. want: `internal server error`,
  164. statusCode: http.StatusInternalServerError,
  165. },
  166. {
  167. name: "duplicate user",
  168. body: `{"screen_name":"userA", "password":"thepassword"}`,
  169. user: func() state.User {
  170. user := state.User{
  171. AuthKey: "theAuthKey",
  172. ScreenName: "userA",
  173. }
  174. assert.NoError(t, user.HashPassword("thepassword"))
  175. return user
  176. }(),
  177. userHandlerErr: state.ErrDupUser,
  178. want: `user already exists`,
  179. statusCode: http.StatusConflict,
  180. },
  181. }
  182. for _, tc := range tt {
  183. t.Run(tc.name, func(t *testing.T) {
  184. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  185. responseRecorder := httptest.NewRecorder()
  186. userManager := newMockUserManager(t)
  187. if tc.user.ScreenName != "" {
  188. userManager.EXPECT().
  189. InsertUser(tc.user).
  190. Return(tc.userHandlerErr)
  191. }
  192. newUser := func() state.User {
  193. return tc.user
  194. }
  195. userHandler(responseRecorder, request, userManager, newUser, slog.Default())
  196. if responseRecorder.Code != tc.statusCode {
  197. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  198. }
  199. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  200. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  201. }
  202. })
  203. }
  204. }
  205. func TestUserHandler_DELETE(t *testing.T) {
  206. tt := []struct {
  207. name string
  208. body string
  209. user state.User
  210. userHandlerErr error
  211. want string
  212. statusCode int
  213. }{
  214. {
  215. name: "with valid user",
  216. body: `{"screen_name":"userA"}`,
  217. user: state.User{
  218. ScreenName: "userA",
  219. },
  220. want: `User account successfully deleted.`,
  221. statusCode: http.StatusNoContent,
  222. },
  223. {
  224. name: "with non-existent user",
  225. body: `{"screen_name":"userA"}`,
  226. user: state.User{
  227. ScreenName: "userA",
  228. },
  229. userHandlerErr: state.ErrNoUser,
  230. want: `user does not exist`,
  231. statusCode: http.StatusNotFound,
  232. },
  233. {
  234. name: "with malformed body",
  235. body: `{"screen_name":"userA"`,
  236. user: state.User{},
  237. want: `malformed input`,
  238. statusCode: http.StatusBadRequest,
  239. },
  240. {
  241. name: "user handler error",
  242. body: `{"screen_name":"userA"}`,
  243. user: state.User{
  244. ScreenName: "userA",
  245. },
  246. userHandlerErr: io.EOF,
  247. want: `internal server error`,
  248. statusCode: http.StatusInternalServerError,
  249. },
  250. }
  251. for _, tc := range tt {
  252. t.Run(tc.name, func(t *testing.T) {
  253. request := httptest.NewRequest(http.MethodDelete, "/user", strings.NewReader(tc.body))
  254. responseRecorder := httptest.NewRecorder()
  255. userManager := newMockUserManager(t)
  256. if tc.user.ScreenName != "" {
  257. userManager.EXPECT().
  258. DeleteUser(tc.user.ScreenName).
  259. Return(tc.userHandlerErr)
  260. }
  261. userHandler(responseRecorder, request, userManager, nil, slog.Default())
  262. if responseRecorder.Code != tc.statusCode {
  263. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  264. }
  265. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  266. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  267. }
  268. })
  269. }
  270. }
  271. func TestUserPasswordHandler_PUT(t *testing.T) {
  272. tt := []struct {
  273. name string
  274. body string
  275. user state.User
  276. userHandlerErr error
  277. want string
  278. statusCode int
  279. }{
  280. {
  281. name: "with valid password",
  282. body: `{"screen_name":"userA", "password":"thepassword"}`,
  283. user: func() state.User {
  284. user := state.User{
  285. AuthKey: "theAuthKey",
  286. ScreenName: "userA",
  287. }
  288. assert.NoError(t, user.HashPassword("thepassword"))
  289. return user
  290. }(),
  291. want: ``,
  292. statusCode: http.StatusNoContent,
  293. },
  294. {
  295. name: "with malformed body",
  296. body: `{"screen_name":"userA", "password":"thepassword"`,
  297. user: state.User{},
  298. want: `malformed input`,
  299. statusCode: http.StatusBadRequest,
  300. },
  301. {
  302. name: "user password handler error",
  303. body: `{"screen_name":"userA", "password":"thepassword"}`,
  304. user: func() state.User {
  305. user := state.User{
  306. AuthKey: "theAuthKey",
  307. ScreenName: "userA",
  308. }
  309. assert.NoError(t, user.HashPassword("thepassword"))
  310. return user
  311. }(),
  312. userHandlerErr: io.EOF,
  313. want: `internal server error`,
  314. statusCode: http.StatusInternalServerError,
  315. },
  316. {
  317. name: "user doesn't exist",
  318. body: `{"screen_name":"userA", "password":"thepassword"}`,
  319. user: func() state.User {
  320. user := state.User{
  321. AuthKey: "theAuthKey",
  322. ScreenName: "userA",
  323. }
  324. assert.NoError(t, user.HashPassword("thepassword"))
  325. return user
  326. }(),
  327. userHandlerErr: state.ErrNoUser,
  328. want: `user does not exist`,
  329. statusCode: http.StatusNotFound,
  330. },
  331. }
  332. for _, tc := range tt {
  333. t.Run(tc.name, func(t *testing.T) {
  334. request := httptest.NewRequest(http.MethodPut, "/user", strings.NewReader(tc.body))
  335. responseRecorder := httptest.NewRecorder()
  336. userManager := newMockUserManager(t)
  337. if tc.user.ScreenName != "" {
  338. userManager.EXPECT().
  339. SetUserPassword(tc.user).
  340. Return(tc.userHandlerErr)
  341. }
  342. uf := func() state.User {
  343. return tc.user
  344. }
  345. userPasswordHandler(responseRecorder, request, userManager, uf, slog.Default())
  346. if responseRecorder.Code != tc.statusCode {
  347. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  348. }
  349. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  350. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  351. }
  352. })
  353. }
  354. }
  355. func TestUserHandler_DisallowedMethod(t *testing.T) {
  356. request := httptest.NewRequest(http.MethodPut, "/user", nil)
  357. responseRecorder := httptest.NewRecorder()
  358. userHandler(responseRecorder, request, nil, nil, nil)
  359. wantCode := http.StatusMethodNotAllowed
  360. if responseRecorder.Code != wantCode {
  361. t.Errorf("want status '%d', got '%d'", http.StatusMethodNotAllowed, responseRecorder.Code)
  362. }
  363. wantBody := `method not allowed`
  364. if strings.TrimSpace(responseRecorder.Body.String()) != wantBody {
  365. t.Errorf("want '%s', got '%s'", wantBody, responseRecorder.Body)
  366. }
  367. }