mgmt_api_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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 TestUserPasswordHandler_PUT(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 password",
  216. body: `{"screen_name":"userA", "password":"thepassword"}`,
  217. user: func() state.User {
  218. user := state.User{
  219. AuthKey: "theAuthKey",
  220. ScreenName: "userA",
  221. }
  222. assert.NoError(t, user.HashPassword("thepassword"))
  223. return user
  224. }(),
  225. want: ``,
  226. statusCode: http.StatusNoContent,
  227. },
  228. {
  229. name: "with malformed body",
  230. body: `{"screen_name":"userA", "password":"thepassword"`,
  231. user: state.User{},
  232. want: `malformed input`,
  233. statusCode: http.StatusBadRequest,
  234. },
  235. {
  236. name: "user password handler error",
  237. body: `{"screen_name":"userA", "password":"thepassword"}`,
  238. user: func() state.User {
  239. user := state.User{
  240. AuthKey: "theAuthKey",
  241. ScreenName: "userA",
  242. }
  243. assert.NoError(t, user.HashPassword("thepassword"))
  244. return user
  245. }(),
  246. userHandlerErr: io.EOF,
  247. want: `internal server error`,
  248. statusCode: http.StatusInternalServerError,
  249. },
  250. {
  251. name: "user doesn't exist",
  252. body: `{"screen_name":"userA", "password":"thepassword"}`,
  253. user: func() state.User {
  254. user := state.User{
  255. AuthKey: "theAuthKey",
  256. ScreenName: "userA",
  257. }
  258. assert.NoError(t, user.HashPassword("thepassword"))
  259. return user
  260. }(),
  261. userHandlerErr: state.ErrNoUser,
  262. want: `user does not exist`,
  263. statusCode: http.StatusNotFound,
  264. },
  265. }
  266. for _, tc := range tt {
  267. t.Run(tc.name, func(t *testing.T) {
  268. request := httptest.NewRequest(http.MethodPut, "/user", strings.NewReader(tc.body))
  269. responseRecorder := httptest.NewRecorder()
  270. userManager := newMockUserManager(t)
  271. if tc.user.ScreenName != "" {
  272. userManager.EXPECT().
  273. SetUserPassword(tc.user).
  274. Return(tc.userHandlerErr)
  275. }
  276. uf := func() state.User {
  277. return tc.user
  278. }
  279. userPasswordHandler(responseRecorder, request, userManager, uf, slog.Default())
  280. if responseRecorder.Code != tc.statusCode {
  281. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  282. }
  283. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  284. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  285. }
  286. })
  287. }
  288. }
  289. func TestUserHandler_DisallowedMethod(t *testing.T) {
  290. request := httptest.NewRequest(http.MethodPut, "/user", nil)
  291. responseRecorder := httptest.NewRecorder()
  292. userHandler(responseRecorder, request, nil, nil, nil)
  293. wantCode := http.StatusMethodNotAllowed
  294. if responseRecorder.Code != wantCode {
  295. t.Errorf("want status '%d', got '%d'", http.StatusMethodNotAllowed, responseRecorder.Code)
  296. }
  297. wantBody := `method not allowed`
  298. if strings.TrimSpace(responseRecorder.Body.String()) != wantBody {
  299. t.Errorf("want '%s', got '%s'", wantBody, responseRecorder.Body)
  300. }
  301. }