mgmt_api_test.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  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. tt := []struct {
  279. name string
  280. requestScreenName state.IdentScreenName
  281. want string
  282. statusCode int
  283. contentType string
  284. mockParams mockParams
  285. }{
  286. {
  287. name: "invalid account",
  288. requestScreenName: state.NewIdentScreenName("userA"),
  289. want: `user not found`,
  290. statusCode: http.StatusNotFound,
  291. contentType: "text/plain; charset=utf-8",
  292. mockParams: mockParams{
  293. userManagerParams: userManagerParams{
  294. getUserParams: getUserParams{
  295. {
  296. screenName: state.NewIdentScreenName("userA"),
  297. result: nil,
  298. },
  299. },
  300. },
  301. },
  302. },
  303. {
  304. name: "account with buddy icon",
  305. requestScreenName: state.NewIdentScreenName("userA"),
  306. want: string([]byte{'i', 'c', 'o', 'n'}),
  307. statusCode: http.StatusOK,
  308. contentType: "image/gif",
  309. mockParams: mockParams{
  310. userManagerParams: userManagerParams{
  311. getUserParams: getUserParams{
  312. {
  313. screenName: state.NewIdentScreenName("userA"),
  314. result: &state.User{
  315. DisplayScreenName: "userA",
  316. IdentScreenName: state.NewIdentScreenName("userA"),
  317. },
  318. },
  319. },
  320. },
  321. feedBagRetrieverParams: feedBagRetrieverParams{
  322. buddyIconRefByNameParams: buddyIconRefByNameParams{
  323. {
  324. screenName: state.NewIdentScreenName("userA"),
  325. result: &wire.BARTID{
  326. Type: wire.BARTTypesBuddyIcon,
  327. BARTInfo: wire.BARTInfo{
  328. Flags: 0x00,
  329. Hash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  330. },
  331. },
  332. },
  333. },
  334. },
  335. bartRetrieverParams: bartRetrieverParams{
  336. bartRetrieveParams: bartRetrieveParams{
  337. {
  338. itemHash: []byte{'t', 'h', 'e', 'h', 'a', 's', 'h'},
  339. result: []byte{'i', 'c', 'o', 'n'},
  340. },
  341. },
  342. },
  343. },
  344. },
  345. {
  346. name: "account with cleared buddy icon",
  347. requestScreenName: state.NewIdentScreenName("userA"),
  348. want: "icon not found",
  349. statusCode: http.StatusNotFound,
  350. contentType: "text/plain; charset=utf-8",
  351. mockParams: mockParams{
  352. userManagerParams: userManagerParams{
  353. getUserParams: getUserParams{
  354. {
  355. screenName: state.NewIdentScreenName("userA"),
  356. result: &state.User{
  357. DisplayScreenName: "userA",
  358. IdentScreenName: state.NewIdentScreenName("userA"),
  359. },
  360. },
  361. },
  362. },
  363. feedBagRetrieverParams: feedBagRetrieverParams{
  364. buddyIconRefByNameParams: buddyIconRefByNameParams{
  365. {
  366. screenName: state.NewIdentScreenName("userA"),
  367. result: &wire.BARTID{
  368. Type: wire.BARTTypesBuddyIcon,
  369. BARTInfo: wire.BARTInfo{
  370. Flags: 0x00,
  371. Hash: wire.GetClearIconHash(),
  372. },
  373. },
  374. },
  375. },
  376. },
  377. },
  378. },
  379. {
  380. name: "account with no buddy icon",
  381. requestScreenName: state.NewIdentScreenName("userA"),
  382. want: "icon not found",
  383. statusCode: http.StatusNotFound,
  384. contentType: "text/plain; charset=utf-8",
  385. mockParams: mockParams{
  386. userManagerParams: userManagerParams{
  387. getUserParams: getUserParams{
  388. {
  389. screenName: state.NewIdentScreenName("userA"),
  390. result: &state.User{
  391. DisplayScreenName: "userA",
  392. IdentScreenName: state.NewIdentScreenName("userA"),
  393. },
  394. },
  395. },
  396. },
  397. feedBagRetrieverParams: feedBagRetrieverParams{
  398. buddyIconRefByNameParams: buddyIconRefByNameParams{
  399. {
  400. screenName: state.NewIdentScreenName("userA"),
  401. result: nil,
  402. },
  403. },
  404. },
  405. },
  406. },
  407. }
  408. for _, tc := range tt {
  409. t.Run(tc.name, func(t *testing.T) {
  410. request := httptest.NewRequest(http.MethodGet, "/user/"+tc.requestScreenName.String()+"/icon", nil)
  411. request.SetPathValue("screenname", tc.requestScreenName.String())
  412. responseRecorder := httptest.NewRecorder()
  413. userManager := newMockUserManager(t)
  414. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  415. userManager.EXPECT().
  416. User(params.screenName).
  417. Return(params.result, params.err)
  418. }
  419. feedbagRetriever := newMockFeedBagRetriever(t)
  420. for _, params := range tc.mockParams.feedBagRetrieverParams.buddyIconRefByNameParams {
  421. feedbagRetriever.EXPECT().
  422. BuddyIconRefByName(params.screenName).
  423. Return(params.result, params.err)
  424. }
  425. bartRetriever := newMockBARTRetriever(t)
  426. for _, params := range tc.mockParams.bartRetrieverParams.bartRetrieveParams {
  427. bartRetriever.EXPECT().
  428. BARTRetrieve(params.itemHash).
  429. Return(params.result, params.err)
  430. }
  431. getUserBuddyIconHandler(responseRecorder, request, userManager, feedbagRetriever, bartRetriever, slog.Default())
  432. if responseRecorder.Code != tc.statusCode {
  433. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  434. }
  435. contentType := responseRecorder.Header().Get("Content-Type")
  436. if contentType != tc.contentType {
  437. t.Errorf("Want content type '%s', got '%s'", tc.contentType, contentType)
  438. }
  439. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  440. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  441. }
  442. })
  443. }
  444. }
  445. func TestUserHandler_GET(t *testing.T) {
  446. tt := []struct {
  447. name string
  448. want string
  449. statusCode int
  450. mockParams mockParams
  451. }{
  452. {
  453. name: "empty user store",
  454. want: `[]`,
  455. statusCode: http.StatusOK,
  456. mockParams: mockParams{
  457. userManagerParams: userManagerParams{
  458. allUsersParams: allUsersParams{
  459. {
  460. result: []state.User{},
  461. },
  462. },
  463. },
  464. },
  465. },
  466. {
  467. name: "user store containing 3 users",
  468. 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}]`,
  469. statusCode: http.StatusOK,
  470. mockParams: mockParams{
  471. userManagerParams: userManagerParams{
  472. allUsersParams: allUsersParams{
  473. {
  474. result: []state.User{
  475. {
  476. DisplayScreenName: "userA",
  477. IdentScreenName: state.NewIdentScreenName("userA"),
  478. },
  479. {
  480. DisplayScreenName: "userB",
  481. IdentScreenName: state.NewIdentScreenName("userB"),
  482. },
  483. {
  484. DisplayScreenName: "100003",
  485. IdentScreenName: state.NewIdentScreenName("100003"),
  486. IsICQ: true,
  487. },
  488. },
  489. },
  490. },
  491. },
  492. },
  493. },
  494. {
  495. name: "user handler error",
  496. want: `internal server error`,
  497. statusCode: http.StatusInternalServerError,
  498. mockParams: mockParams{
  499. userManagerParams: userManagerParams{
  500. allUsersParams: allUsersParams{
  501. {
  502. result: []state.User{},
  503. err: io.EOF,
  504. },
  505. },
  506. },
  507. },
  508. },
  509. }
  510. for _, tc := range tt {
  511. t.Run(tc.name, func(t *testing.T) {
  512. responseRecorder := httptest.NewRecorder()
  513. userManager := newMockUserManager(t)
  514. for _, params := range tc.mockParams.userManagerParams.allUsersParams {
  515. userManager.EXPECT().
  516. AllUsers().
  517. Return(params.result, params.err)
  518. }
  519. getUserHandler(responseRecorder, userManager, slog.Default())
  520. if responseRecorder.Code != tc.statusCode {
  521. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  522. }
  523. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  524. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  525. }
  526. })
  527. }
  528. }
  529. func TestUserHandler_POST(t *testing.T) {
  530. tt := []struct {
  531. name string
  532. body string
  533. UUID uuid.UUID
  534. want string
  535. password string
  536. statusCode int
  537. mockParams mockParams
  538. }{
  539. {
  540. name: "with valid AIM user",
  541. body: `{"screen_name":"userA", "password":"thepassword"}`,
  542. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  543. want: `User account created successfully.`,
  544. password: "thepassword",
  545. statusCode: http.StatusCreated,
  546. mockParams: mockParams{
  547. userManagerParams: userManagerParams{
  548. insertUserParams: insertUserParams{
  549. {
  550. u: state.User{
  551. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  552. DisplayScreenName: "userA",
  553. IdentScreenName: state.NewIdentScreenName("userA"),
  554. },
  555. err: nil,
  556. },
  557. },
  558. },
  559. },
  560. },
  561. {
  562. name: "with valid ICQ user",
  563. body: `{"screen_name":"100003", "password":"thepass"}`,
  564. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  565. want: `User account created successfully.`,
  566. password: "thepass",
  567. statusCode: http.StatusCreated,
  568. mockParams: mockParams{
  569. userManagerParams: userManagerParams{
  570. insertUserParams: insertUserParams{
  571. {
  572. u: state.User{
  573. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  574. DisplayScreenName: "100003",
  575. IdentScreenName: state.NewIdentScreenName("100003"),
  576. IsICQ: true,
  577. },
  578. err: nil,
  579. },
  580. },
  581. },
  582. },
  583. },
  584. {
  585. name: "with malformed body",
  586. body: `{"screen_name":"userA", "password":"thepassword"`, // missing closing }
  587. want: `malformed input`,
  588. statusCode: http.StatusBadRequest,
  589. },
  590. {
  591. name: "user handler error",
  592. body: `{"screen_name":"userA", "password":"thepassword"}`,
  593. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  594. want: `internal server error`,
  595. password: "thepassword",
  596. statusCode: http.StatusInternalServerError,
  597. mockParams: mockParams{
  598. userManagerParams: userManagerParams{
  599. insertUserParams: insertUserParams{
  600. {
  601. u: state.User{
  602. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  603. DisplayScreenName: "userA",
  604. IdentScreenName: state.NewIdentScreenName("userA"),
  605. },
  606. err: io.EOF,
  607. },
  608. },
  609. },
  610. },
  611. },
  612. {
  613. name: "duplicate user",
  614. body: `{"screen_name":"userA", "password":"thepassword"}`,
  615. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  616. want: `user already exists`,
  617. password: "thepassword",
  618. statusCode: http.StatusConflict,
  619. mockParams: mockParams{
  620. userManagerParams: userManagerParams{
  621. insertUserParams: insertUserParams{
  622. {
  623. u: state.User{
  624. AuthKey: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
  625. DisplayScreenName: "userA",
  626. IdentScreenName: state.NewIdentScreenName("userA"),
  627. },
  628. err: state.ErrDupUser,
  629. },
  630. },
  631. },
  632. },
  633. },
  634. {
  635. name: "invalid AIM screen name",
  636. body: `{"screen_name":"a", "password":"thepassword"}`,
  637. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  638. want: `invalid screen name: screen name must be between 3 and 16 characters`,
  639. statusCode: http.StatusBadRequest,
  640. },
  641. {
  642. name: "invalid AIM password",
  643. body: `{"screen_name":"userA", "password":"1"}`,
  644. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  645. want: `invalid password: invalid password length: password length must be between 4-16 characters`,
  646. statusCode: http.StatusBadRequest,
  647. },
  648. {
  649. name: "invalid ICQ UIN",
  650. body: `{"screen_name":"1000", "password":"thepass"}`,
  651. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  652. want: `invalid uin: uin must be a number in the range 10000-2147483646`,
  653. statusCode: http.StatusBadRequest,
  654. },
  655. {
  656. name: "invalid ICQ password",
  657. body: `{"screen_name":"100003", "password":"thelongpassword"}`,
  658. UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
  659. want: `invalid password: invalid password length: password must be between 6-8 characters`,
  660. statusCode: http.StatusBadRequest,
  661. },
  662. }
  663. for _, tc := range tt {
  664. t.Run(tc.name, func(t *testing.T) {
  665. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  666. responseRecorder := httptest.NewRecorder()
  667. userManager := newMockUserManager(t)
  668. for _, params := range tc.mockParams.userManagerParams.insertUserParams {
  669. assert.NoError(t, params.u.HashPassword(tc.password))
  670. userManager.EXPECT().
  671. InsertUser(params.u).
  672. Return(params.err)
  673. }
  674. newUUID := func() uuid.UUID { return tc.UUID }
  675. postUserHandler(responseRecorder, request, userManager, newUUID, slog.Default())
  676. if responseRecorder.Code != tc.statusCode {
  677. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  678. }
  679. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  680. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  681. }
  682. })
  683. }
  684. }
  685. func TestUserHandler_DELETE(t *testing.T) {
  686. tt := []struct {
  687. name string
  688. body string
  689. want string
  690. statusCode int
  691. mockParams mockParams
  692. }{
  693. {
  694. name: "with valid user",
  695. body: `{"screen_name":"userA"}`,
  696. want: `User account successfully deleted.`,
  697. statusCode: http.StatusNoContent,
  698. mockParams: mockParams{
  699. userManagerParams: userManagerParams{
  700. deleteUserParams: deleteUserParams{
  701. {
  702. screenName: state.NewIdentScreenName("userA"),
  703. },
  704. },
  705. },
  706. },
  707. },
  708. {
  709. name: "with non-existent user",
  710. body: `{"screen_name":"userA"}`,
  711. want: `user does not exist`,
  712. statusCode: http.StatusNotFound,
  713. mockParams: mockParams{
  714. userManagerParams: userManagerParams{
  715. deleteUserParams: deleteUserParams{
  716. {
  717. screenName: state.NewIdentScreenName("userA"),
  718. err: state.ErrNoUser,
  719. },
  720. },
  721. },
  722. },
  723. },
  724. {
  725. name: "with malformed body",
  726. body: `{"screen_name":"userA"`, // missing closing }
  727. want: `malformed input`,
  728. statusCode: http.StatusBadRequest,
  729. },
  730. {
  731. name: "user handler error",
  732. body: `{"screen_name":"userA"}`,
  733. want: `internal server error`,
  734. statusCode: http.StatusInternalServerError,
  735. mockParams: mockParams{
  736. userManagerParams: userManagerParams{
  737. deleteUserParams: deleteUserParams{
  738. {
  739. screenName: state.NewIdentScreenName("userA"),
  740. err: io.EOF,
  741. },
  742. },
  743. },
  744. },
  745. },
  746. }
  747. for _, tc := range tt {
  748. t.Run(tc.name, func(t *testing.T) {
  749. request := httptest.NewRequest(http.MethodDelete, "/user", strings.NewReader(tc.body))
  750. responseRecorder := httptest.NewRecorder()
  751. userManager := newMockUserManager(t)
  752. for _, params := range tc.mockParams.userManagerParams.deleteUserParams {
  753. userManager.EXPECT().
  754. DeleteUser(params.screenName).
  755. Return(params.err)
  756. }
  757. deleteUserHandler(responseRecorder, request, userManager, slog.Default())
  758. if responseRecorder.Code != tc.statusCode {
  759. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  760. }
  761. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  762. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  763. }
  764. })
  765. }
  766. }
  767. func TestUserPasswordHandler_PUT(t *testing.T) {
  768. tt := []struct {
  769. name string
  770. body string
  771. want string
  772. statusCode int
  773. mockParams mockParams
  774. }{
  775. {
  776. name: "user with valid password",
  777. body: `{"screen_name":"userA", "password":"thenewpassword"}`,
  778. want: `Password successfully reset.`,
  779. statusCode: http.StatusNoContent,
  780. mockParams: mockParams{
  781. userManagerParams: userManagerParams{
  782. setUserPasswordParams: setUserPasswordParams{
  783. {
  784. screenName: state.NewIdentScreenName("userA"),
  785. newPassword: "thenewpassword",
  786. },
  787. },
  788. },
  789. },
  790. },
  791. {
  792. name: "user with invalid password",
  793. body: `{"screen_name":"userA", "password":"a"}`,
  794. want: `invalid password length`,
  795. statusCode: http.StatusBadRequest,
  796. mockParams: mockParams{
  797. userManagerParams: userManagerParams{
  798. setUserPasswordParams: setUserPasswordParams{
  799. {
  800. screenName: state.NewIdentScreenName("userA"),
  801. newPassword: "a",
  802. err: state.ErrPasswordInvalid,
  803. },
  804. },
  805. },
  806. },
  807. },
  808. {
  809. name: "with malformed body",
  810. body: `{"screen_name":"userA", "password":"thepassword"`, // missing closing }
  811. want: `malformed input`,
  812. statusCode: http.StatusBadRequest,
  813. },
  814. {
  815. name: "password updater returns runtime error",
  816. body: `{"screen_name":"userA", "password":"thepassword"}`,
  817. want: `internal server error`,
  818. statusCode: http.StatusInternalServerError,
  819. mockParams: mockParams{
  820. userManagerParams: userManagerParams{
  821. setUserPasswordParams: setUserPasswordParams{
  822. {
  823. screenName: state.NewIdentScreenName("userA"),
  824. newPassword: "thepassword",
  825. err: io.EOF,
  826. },
  827. },
  828. },
  829. },
  830. },
  831. {
  832. name: "user doesn't exist",
  833. body: `{"screen_name":"userA", "password":"thepassword"}`,
  834. want: `user does not exist`,
  835. statusCode: http.StatusNotFound,
  836. mockParams: mockParams{
  837. userManagerParams: userManagerParams{
  838. setUserPasswordParams: setUserPasswordParams{
  839. {
  840. screenName: state.NewIdentScreenName("userA"),
  841. newPassword: "thepassword",
  842. err: state.ErrNoUser,
  843. },
  844. },
  845. },
  846. },
  847. },
  848. }
  849. for _, tc := range tt {
  850. t.Run(tc.name, func(t *testing.T) {
  851. request := httptest.NewRequest(http.MethodPut, "/user", strings.NewReader(tc.body))
  852. responseRecorder := httptest.NewRecorder()
  853. userManager := newMockUserManager(t)
  854. for _, params := range tc.mockParams.userManagerParams.setUserPasswordParams {
  855. userManager.EXPECT().
  856. SetUserPassword(params.screenName, params.newPassword).
  857. Return(params.err)
  858. }
  859. putUserPasswordHandler(responseRecorder, request, userManager, slog.Default())
  860. if responseRecorder.Code != tc.statusCode {
  861. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  862. }
  863. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  864. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  865. }
  866. })
  867. }
  868. }
  869. func TestPublicChatHandler_GET(t *testing.T) {
  870. fnNewSess := func(screenName string) *state.Session {
  871. sess := state.NewSession()
  872. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  873. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  874. return sess
  875. }
  876. chatRoom1 := state.NewChatRoom("chat-room-1-name", state.NewIdentScreenName("chat-room-1-creator"), state.PublicExchange)
  877. chatRoom2 := state.NewChatRoom("chat-room-2-name", state.NewIdentScreenName("chat-room-1-creator"), state.PublicExchange)
  878. tt := []struct {
  879. name string
  880. want string
  881. statusCode int
  882. mockParams mockParams
  883. }{
  884. {
  885. name: "multiple chat rooms with participants",
  886. 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"}]}]`,
  887. statusCode: http.StatusOK,
  888. mockParams: mockParams{
  889. chatRoomRetrieverParams: chatRoomRetrieverParams{
  890. allChatRoomsParams: allChatRoomsParams{
  891. {
  892. exchange: state.PublicExchange,
  893. result: []state.ChatRoom{
  894. chatRoom1,
  895. chatRoom2,
  896. },
  897. },
  898. },
  899. },
  900. chatSessionRetrieverParams: chatSessionRetrieverParams{
  901. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  902. {
  903. cookie: chatRoom1.Cookie(),
  904. result: []*state.Session{
  905. fnNewSess("userA"),
  906. fnNewSess("userB"),
  907. },
  908. },
  909. {
  910. cookie: chatRoom2.Cookie(),
  911. result: []*state.Session{
  912. fnNewSess("userC"),
  913. fnNewSess("userD"),
  914. },
  915. },
  916. },
  917. },
  918. },
  919. },
  920. {
  921. name: "chat room without participants",
  922. want: `[{"name":"chat-room-1-name","create_time":"0001-01-01T00:00:00Z","url":"aim:gochat?roomname=chat-room-1-name\u0026exchange=5","participants":[]}]`,
  923. statusCode: http.StatusOK,
  924. mockParams: mockParams{
  925. chatRoomRetrieverParams: chatRoomRetrieverParams{
  926. allChatRoomsParams: allChatRoomsParams{
  927. {
  928. exchange: state.PublicExchange,
  929. result: []state.ChatRoom{
  930. chatRoom1,
  931. },
  932. },
  933. },
  934. },
  935. chatSessionRetrieverParams: chatSessionRetrieverParams{
  936. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  937. {
  938. cookie: chatRoom1.Cookie(),
  939. result: []*state.Session{},
  940. },
  941. },
  942. },
  943. },
  944. },
  945. {
  946. name: "no chat rooms",
  947. want: `[]`,
  948. statusCode: http.StatusOK,
  949. mockParams: mockParams{
  950. chatRoomRetrieverParams: chatRoomRetrieverParams{
  951. allChatRoomsParams: allChatRoomsParams{
  952. {
  953. exchange: state.PublicExchange,
  954. result: []state.ChatRoom{},
  955. },
  956. },
  957. },
  958. },
  959. },
  960. }
  961. for _, tc := range tt {
  962. t.Run(tc.name, func(t *testing.T) {
  963. request := httptest.NewRequest(http.MethodGet, "/chat/room/public", nil)
  964. responseRecorder := httptest.NewRecorder()
  965. chatRoomRetriever := newMockChatRoomRetriever(t)
  966. for _, params := range tc.mockParams.chatRoomRetrieverParams.allChatRoomsParams {
  967. chatRoomRetriever.EXPECT().
  968. AllChatRooms(params.exchange).
  969. Return(params.result, params.err)
  970. }
  971. chatSessionRetriever := newMockChatSessionRetriever(t)
  972. for _, params := range tc.mockParams.chatSessionRetrieverParams.chatSessionRetrieverAllSessionsParams {
  973. chatSessionRetriever.EXPECT().
  974. AllSessions(params.cookie).
  975. Return(params.result)
  976. }
  977. getPublicChatHandler(responseRecorder, request, chatRoomRetriever, chatSessionRetriever, slog.Default())
  978. if responseRecorder.Code != tc.statusCode {
  979. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  980. }
  981. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  982. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  983. }
  984. })
  985. }
  986. }
  987. func TestPrivateChatHandler_GET(t *testing.T) {
  988. fnNewSess := func(screenName string) *state.Session {
  989. sess := state.NewSession()
  990. sess.SetIdentScreenName(state.NewIdentScreenName(screenName))
  991. sess.SetDisplayScreenName(state.DisplayScreenName(screenName))
  992. return sess
  993. }
  994. chatRoom1 := state.NewChatRoom("chat-room-1-name", state.NewIdentScreenName("chat-room-1-creator"), state.PrivateExchange)
  995. chatRoom2 := state.NewChatRoom("chat-room-2-name", state.NewIdentScreenName("chat-room-2-creator"), state.PrivateExchange)
  996. tt := []struct {
  997. name string
  998. want string
  999. statusCode int
  1000. mockParams mockParams
  1001. }{
  1002. {
  1003. name: "multiple chat rooms with participants",
  1004. 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"}]}]`,
  1005. statusCode: http.StatusOK,
  1006. mockParams: mockParams{
  1007. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1008. allChatRoomsParams: allChatRoomsParams{
  1009. {
  1010. exchange: state.PrivateExchange,
  1011. result: []state.ChatRoom{
  1012. chatRoom1,
  1013. chatRoom2,
  1014. },
  1015. },
  1016. },
  1017. },
  1018. chatSessionRetrieverParams: chatSessionRetrieverParams{
  1019. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  1020. {
  1021. cookie: chatRoom1.Cookie(),
  1022. result: []*state.Session{
  1023. fnNewSess("userA"),
  1024. fnNewSess("userB"),
  1025. },
  1026. },
  1027. {
  1028. cookie: chatRoom2.Cookie(),
  1029. result: []*state.Session{
  1030. fnNewSess("userC"),
  1031. fnNewSess("userD"),
  1032. },
  1033. },
  1034. },
  1035. },
  1036. },
  1037. },
  1038. {
  1039. name: "chat room without participants",
  1040. 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":[]}]`,
  1041. statusCode: http.StatusOK,
  1042. mockParams: mockParams{
  1043. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1044. allChatRoomsParams: allChatRoomsParams{
  1045. {
  1046. exchange: state.PrivateExchange,
  1047. result: []state.ChatRoom{
  1048. chatRoom1,
  1049. },
  1050. },
  1051. },
  1052. },
  1053. chatSessionRetrieverParams: chatSessionRetrieverParams{
  1054. chatSessionRetrieverAllSessionsParams: chatSessionRetrieverAllSessionsParams{
  1055. {
  1056. cookie: chatRoom1.Cookie(),
  1057. result: []*state.Session{},
  1058. },
  1059. },
  1060. },
  1061. },
  1062. },
  1063. {
  1064. name: "no chat rooms",
  1065. want: `[]`,
  1066. statusCode: http.StatusOK,
  1067. mockParams: mockParams{
  1068. chatRoomRetrieverParams: chatRoomRetrieverParams{
  1069. allChatRoomsParams: allChatRoomsParams{
  1070. {
  1071. exchange: state.PrivateExchange,
  1072. result: []state.ChatRoom{},
  1073. },
  1074. },
  1075. },
  1076. },
  1077. },
  1078. }
  1079. for _, tc := range tt {
  1080. t.Run(tc.name, func(t *testing.T) {
  1081. request := httptest.NewRequest(http.MethodGet, "/chat/room/private", nil)
  1082. responseRecorder := httptest.NewRecorder()
  1083. chatRoomRetriever := newMockChatRoomRetriever(t)
  1084. for _, params := range tc.mockParams.chatRoomRetrieverParams.allChatRoomsParams {
  1085. chatRoomRetriever.EXPECT().
  1086. AllChatRooms(params.exchange).
  1087. Return(params.result, params.err)
  1088. }
  1089. chatSessionRetriever := newMockChatSessionRetriever(t)
  1090. for _, params := range tc.mockParams.chatSessionRetrieverParams.chatSessionRetrieverAllSessionsParams {
  1091. chatSessionRetriever.EXPECT().
  1092. AllSessions(params.cookie).
  1093. Return(params.result)
  1094. }
  1095. getPrivateChatHandler(responseRecorder, request, chatRoomRetriever, chatSessionRetriever, slog.Default())
  1096. if responseRecorder.Code != tc.statusCode {
  1097. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1098. }
  1099. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1100. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  1101. }
  1102. })
  1103. }
  1104. }
  1105. func TestInstantMessageHandler_POST(t *testing.T) {
  1106. type relayToScreenNameInputs struct {
  1107. sender state.IdentScreenName
  1108. recipient state.IdentScreenName
  1109. msg string
  1110. }
  1111. tt := []struct {
  1112. name string
  1113. relayToScreenNameInputs []relayToScreenNameInputs
  1114. body string
  1115. want string
  1116. statusCode int
  1117. }{
  1118. {
  1119. name: "send an instant message",
  1120. relayToScreenNameInputs: []relayToScreenNameInputs{
  1121. {
  1122. sender: state.NewIdentScreenName("sender_sn"),
  1123. recipient: state.NewIdentScreenName("recip_sn"),
  1124. msg: "hello world!",
  1125. },
  1126. },
  1127. body: `{"from":"sender_sn","to":"recip_sn","text":"hello world!"}`,
  1128. want: `Message sent successfully.`,
  1129. statusCode: http.StatusOK,
  1130. },
  1131. {
  1132. name: "with malformed body",
  1133. body: `{"screen_name":"userA", "password":"thepassword"`,
  1134. want: `malformed input`,
  1135. statusCode: http.StatusBadRequest,
  1136. },
  1137. }
  1138. for _, tc := range tt {
  1139. t.Run(tc.name, func(t *testing.T) {
  1140. request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
  1141. responseRecorder := httptest.NewRecorder()
  1142. messageRelayer := newMockMessageRelayer(t)
  1143. for _, params := range tc.relayToScreenNameInputs {
  1144. validateSNAC := func(msg wire.SNACMessage) bool {
  1145. body := msg.Body.(wire.SNAC_0x04_0x07_ICBMChannelMsgToClient)
  1146. assert.Equal(t, params.sender.String(), body.TLVUserInfo.ScreenName)
  1147. b, ok := body.Slice(wire.ICBMTLVAOLIMData)
  1148. assert.True(t, ok)
  1149. txt, err := wire.UnmarshalICBMMessageText(b)
  1150. assert.NoError(t, err)
  1151. assert.Equal(t, params.msg, txt)
  1152. return true
  1153. }
  1154. messageRelayer.EXPECT().
  1155. RelayToScreenName(mock.Anything, params.recipient, mock.MatchedBy(validateSNAC))
  1156. }
  1157. postInstantMessageHandler(responseRecorder, request, messageRelayer, slog.Default())
  1158. if responseRecorder.Code != tc.statusCode {
  1159. t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1160. }
  1161. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1162. t.Errorf("want '%s', got '%s'", tc.want, responseRecorder.Body)
  1163. }
  1164. })
  1165. }
  1166. }
  1167. func TestVersionHandler_GET(t *testing.T) {
  1168. tt := []struct {
  1169. name string
  1170. want string
  1171. statusCode int
  1172. buildInfo config.Build
  1173. }{
  1174. {
  1175. name: "get ras version",
  1176. want: `{"version":"13.3.7","commit":"asdfASDF12345678","date":"2024-03-01"}`,
  1177. statusCode: http.StatusOK,
  1178. buildInfo: config.Build{
  1179. Version: "13.3.7",
  1180. Commit: "asdfASDF12345678",
  1181. Date: "2024-03-01",
  1182. },
  1183. },
  1184. }
  1185. for _, tc := range tt {
  1186. t.Run(tc.name, func(t *testing.T) {
  1187. responseRecorder := httptest.NewRecorder()
  1188. getVersionHandler(responseRecorder, tc.buildInfo)
  1189. if responseRecorder.Code != tc.statusCode {
  1190. t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
  1191. }
  1192. if strings.TrimSpace(responseRecorder.Body.String()) != tc.want {
  1193. t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body)
  1194. }
  1195. })
  1196. }
  1197. }