user_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. package state
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "github.com/mk6i/retro-aim-server/wire"
  8. )
  9. func TestUser_HashPassword(t *testing.T) {
  10. tests := []struct {
  11. name string
  12. user User
  13. password string
  14. expectedWeakMD5 []byte
  15. expectedStrongMD5 []byte
  16. wantError bool
  17. }{
  18. {
  19. name: "Valid AIM password",
  20. user: User{AuthKey: "someAuthKey", IsICQ: false},
  21. password: "validPassword",
  22. expectedWeakMD5: wire.WeakMD5PasswordHash("validPassword", "someAuthKey"),
  23. expectedStrongMD5: wire.StrongMD5PasswordHash("validPassword", "someAuthKey"),
  24. wantError: false,
  25. },
  26. {
  27. name: "Empty AIM password",
  28. user: User{AuthKey: "someAuthKey", IsICQ: false},
  29. password: "",
  30. expectedWeakMD5: nil,
  31. expectedStrongMD5: nil,
  32. wantError: true,
  33. },
  34. {
  35. name: "AIM password too short",
  36. user: User{AuthKey: "someAuthKey", IsICQ: false},
  37. password: "abc",
  38. expectedWeakMD5: nil,
  39. expectedStrongMD5: nil,
  40. wantError: true,
  41. },
  42. {
  43. name: "AIM password too long",
  44. user: User{AuthKey: "someAuthKey", IsICQ: false},
  45. password: "thispasswordistoolong",
  46. expectedWeakMD5: nil,
  47. expectedStrongMD5: nil,
  48. wantError: true,
  49. },
  50. {
  51. name: "Valid ICQ password",
  52. user: User{AuthKey: "someAuthKey", IsICQ: true},
  53. password: "validICQ",
  54. expectedWeakMD5: wire.WeakMD5PasswordHash("validICQ", "someAuthKey"),
  55. expectedStrongMD5: wire.StrongMD5PasswordHash("validICQ", "someAuthKey"),
  56. wantError: false,
  57. },
  58. {
  59. name: "Empty ICQ password",
  60. user: User{AuthKey: "someAuthKey", IsICQ: true},
  61. password: "",
  62. expectedWeakMD5: nil,
  63. expectedStrongMD5: nil,
  64. wantError: true,
  65. },
  66. {
  67. name: "ICQ password too long",
  68. user: User{AuthKey: "someAuthKey", IsICQ: true},
  69. password: "icqpass89",
  70. expectedWeakMD5: nil,
  71. expectedStrongMD5: nil,
  72. wantError: true,
  73. },
  74. }
  75. for _, tt := range tests {
  76. t.Run(tt.name, func(t *testing.T) {
  77. err := tt.user.HashPassword(tt.password)
  78. if tt.wantError {
  79. require.Error(t, err)
  80. } else {
  81. require.NoError(t, err)
  82. assert.Equal(t, tt.expectedWeakMD5, tt.user.WeakMD5Pass)
  83. assert.Equal(t, tt.expectedStrongMD5, tt.user.StrongMD5Pass)
  84. }
  85. })
  86. }
  87. }
  88. func TestAge(t *testing.T) {
  89. tests := []struct {
  90. name string
  91. user User
  92. timeNow func() time.Time
  93. expectedAge uint16
  94. }{
  95. {
  96. name: "Valid birthday, only year is set",
  97. user: User{
  98. ICQMoreInfo: ICQMoreInfo{
  99. BirthYear: 1990,
  100. },
  101. },
  102. timeNow: func() time.Time {
  103. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  104. },
  105. expectedAge: 34,
  106. },
  107. {
  108. name: "Valid birthday, birthday passed this year",
  109. user: User{
  110. ICQMoreInfo: ICQMoreInfo{
  111. BirthYear: 1990,
  112. BirthMonth: 5,
  113. BirthDay: 10,
  114. },
  115. },
  116. timeNow: func() time.Time {
  117. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  118. },
  119. expectedAge: 34,
  120. },
  121. {
  122. name: "Valid birthday, birthday not yet passed this year",
  123. user: User{
  124. ICQMoreInfo: ICQMoreInfo{
  125. BirthYear: 1990,
  126. BirthMonth: 12,
  127. BirthDay: 10,
  128. },
  129. },
  130. timeNow: func() time.Time {
  131. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  132. },
  133. expectedAge: 33,
  134. },
  135. {
  136. name: "Birthday is today",
  137. user: User{
  138. ICQMoreInfo: ICQMoreInfo{
  139. BirthYear: 1990,
  140. BirthMonth: 8,
  141. BirthDay: 1,
  142. },
  143. },
  144. timeNow: func() time.Time {
  145. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  146. },
  147. expectedAge: 34,
  148. },
  149. {
  150. name: "Invalid birthday, year is zero",
  151. user: User{
  152. ICQMoreInfo: ICQMoreInfo{
  153. BirthYear: 0,
  154. BirthMonth: 8,
  155. BirthDay: 1,
  156. },
  157. },
  158. timeNow: func() time.Time {
  159. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  160. },
  161. expectedAge: 0,
  162. },
  163. {
  164. name: "Invalid birthday, day is zero",
  165. user: User{
  166. ICQMoreInfo: ICQMoreInfo{
  167. BirthYear: 1990,
  168. BirthMonth: 8,
  169. BirthDay: 0,
  170. },
  171. },
  172. timeNow: func() time.Time {
  173. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  174. },
  175. expectedAge: 0,
  176. },
  177. {
  178. name: "Invalid birthday, month is zero",
  179. user: User{
  180. ICQMoreInfo: ICQMoreInfo{
  181. BirthYear: 1990,
  182. BirthMonth: 0,
  183. BirthDay: 1,
  184. },
  185. },
  186. timeNow: func() time.Time {
  187. return time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC)
  188. },
  189. expectedAge: 0,
  190. },
  191. }
  192. for _, tt := range tests {
  193. t.Run(tt.name, func(t *testing.T) {
  194. age := tt.user.Age(tt.timeNow)
  195. if age != tt.expectedAge {
  196. t.Errorf("expected age %d, got %d", tt.expectedAge, age)
  197. }
  198. })
  199. }
  200. }
  201. func TestDisplayScreenName_ValidateAIMHandle(t *testing.T) {
  202. tests := []struct {
  203. name string
  204. input DisplayScreenName
  205. wantErr error
  206. }{
  207. {"Valid handle no spaces", "User123", nil},
  208. {"Valid handle with min character count and space", "U SR", nil},
  209. {"Valid handle with min character including letters and numbers", "dj3520", nil},
  210. {"Valid handle with max character count", "JustTheRightSize", nil},
  211. {"Valid handle with max character count and spaces", "Just RightSize", nil},
  212. {"Too short", "Us", ErrAIMHandleLength},
  213. {"Too short due to spaces", "U S", ErrAIMHandleLength},
  214. {"Too long", "ThisIsAReallyLongScreenName", ErrAIMHandleLength},
  215. {"Too many spaces", "User 123 ", ErrAIMHandleLength},
  216. {"Starts with number", "1User", ErrAIMHandleInvalidFormat},
  217. {"Ends with space", "User123 ", ErrAIMHandleInvalidFormat},
  218. {"Contains invalid character", "User@123", ErrAIMHandleInvalidFormat},
  219. }
  220. for _, tt := range tests {
  221. t.Run(tt.name, func(t *testing.T) {
  222. err := tt.input.ValidateAIMHandle()
  223. if tt.wantErr != nil {
  224. assert.ErrorIs(t, err, tt.wantErr, "ValidateAIMHandle() error = %v, wantErr %v", err, tt.wantErr)
  225. } else {
  226. assert.NoError(t, err, "ValidateAIMHandle() error = %v, wantErr %v", err, tt.wantErr)
  227. }
  228. })
  229. }
  230. }
  231. func TestDisplayScreenName_ValidateICQHandle(t *testing.T) {
  232. tests := []struct {
  233. name string
  234. input DisplayScreenName
  235. wantErr error
  236. }{
  237. {"Valid UIN", "123456", nil},
  238. {"Too low", "9999", ErrICQUINInvalidFormat},
  239. {"Too high", "2147483647", ErrICQUINInvalidFormat},
  240. {"Non-numeric", "abcd", ErrICQUINInvalidFormat},
  241. }
  242. for _, tt := range tests {
  243. t.Run(tt.name, func(t *testing.T) {
  244. err := tt.input.ValidateUIN()
  245. if tt.wantErr != nil {
  246. assert.ErrorIs(t, err, tt.wantErr, "ValidateUIN() error = %v, wantErr %v", err, tt.wantErr)
  247. } else {
  248. assert.NoError(t, err, "ValidateUIN() error = %v, wantErr %v", err, tt.wantErr)
  249. }
  250. })
  251. }
  252. }
  253. func TestUser_ValidateRoastedPass(t *testing.T) {
  254. tests := []struct {
  255. name string
  256. user User
  257. roastedPass []byte
  258. expected bool
  259. }{
  260. {
  261. name: "Valid roasted password",
  262. user: User{
  263. AuthKey: "testAuthKey",
  264. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  265. },
  266. roastedPass: wire.RoastOSCARPassword([]byte("testPassword")),
  267. expected: true,
  268. },
  269. {
  270. name: "Invalid roasted password",
  271. user: User{
  272. AuthKey: "testAuthKey",
  273. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  274. },
  275. roastedPass: wire.RoastOSCARPassword([]byte("wrongPassword")),
  276. expected: false,
  277. },
  278. {
  279. name: "Empty roasted password",
  280. user: User{
  281. AuthKey: "testAuthKey",
  282. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  283. },
  284. roastedPass: wire.RoastOSCARPassword([]byte("")),
  285. expected: false,
  286. },
  287. {
  288. name: "Empty stored password",
  289. user: User{
  290. AuthKey: "testAuthKey",
  291. WeakMD5Pass: []byte{},
  292. },
  293. roastedPass: wire.RoastOSCARPassword([]byte("testPassword")),
  294. expected: false,
  295. },
  296. }
  297. for _, tt := range tests {
  298. t.Run(tt.name, func(t *testing.T) {
  299. result := tt.user.ValidateRoastedPass(tt.roastedPass)
  300. assert.Equal(t, tt.expected, result)
  301. })
  302. }
  303. }
  304. func TestUser_ValidateRoastedJavaPass(t *testing.T) {
  305. tests := []struct {
  306. name string
  307. user User
  308. roastedPass []byte
  309. expected bool
  310. }{
  311. {
  312. name: "Valid roasted Java password",
  313. user: User{
  314. AuthKey: "testAuthKey",
  315. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  316. },
  317. roastedPass: wire.RoastOSCARJavaPassword([]byte("testPassword")),
  318. expected: true,
  319. },
  320. {
  321. name: "Invalid roasted Java password",
  322. user: User{
  323. AuthKey: "testAuthKey",
  324. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  325. },
  326. roastedPass: wire.RoastOSCARJavaPassword([]byte("wrongPassword")),
  327. expected: false,
  328. },
  329. {
  330. name: "Empty roasted Java password",
  331. user: User{
  332. AuthKey: "testAuthKey",
  333. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  334. },
  335. roastedPass: wire.RoastOSCARJavaPassword([]byte("")),
  336. expected: false,
  337. },
  338. {
  339. name: "Empty stored password",
  340. user: User{
  341. AuthKey: "testAuthKey",
  342. WeakMD5Pass: []byte{},
  343. },
  344. roastedPass: wire.RoastOSCARJavaPassword([]byte("testPassword")),
  345. expected: false,
  346. },
  347. }
  348. for _, tt := range tests {
  349. t.Run(tt.name, func(t *testing.T) {
  350. result := tt.user.ValidateRoastedJavaPass(tt.roastedPass)
  351. assert.Equal(t, tt.expected, result)
  352. })
  353. }
  354. }
  355. func TestUser_ValidateRoastedTOCPass(t *testing.T) {
  356. tests := []struct {
  357. name string
  358. user User
  359. roastedPass []byte
  360. expected bool
  361. }{
  362. {
  363. name: "Valid roasted TOC password",
  364. user: User{
  365. AuthKey: "testAuthKey",
  366. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  367. },
  368. roastedPass: wire.RoastTOCPassword([]byte("testPassword")),
  369. expected: true,
  370. },
  371. {
  372. name: "Invalid roasted TOC password",
  373. user: User{
  374. AuthKey: "testAuthKey",
  375. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  376. },
  377. roastedPass: wire.RoastTOCPassword([]byte("wrongPassword")),
  378. expected: false,
  379. },
  380. {
  381. name: "Empty roasted TOC password",
  382. user: User{
  383. AuthKey: "testAuthKey",
  384. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  385. },
  386. roastedPass: wire.RoastTOCPassword([]byte("")),
  387. expected: false,
  388. },
  389. {
  390. name: "Empty stored password",
  391. user: User{
  392. AuthKey: "testAuthKey",
  393. WeakMD5Pass: []byte{},
  394. },
  395. roastedPass: wire.RoastTOCPassword([]byte("testPassword")),
  396. expected: false,
  397. },
  398. }
  399. for _, tt := range tests {
  400. t.Run(tt.name, func(t *testing.T) {
  401. result := tt.user.ValidateRoastedTOCPass(tt.roastedPass)
  402. assert.Equal(t, tt.expected, result)
  403. })
  404. }
  405. }
  406. func TestUser_ValidatePlaintextPass(t *testing.T) {
  407. tests := []struct {
  408. name string
  409. user User
  410. plaintextPass []byte
  411. expected bool
  412. }{
  413. {
  414. name: "Valid plaintext password",
  415. user: User{
  416. AuthKey: "testAuthKey",
  417. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  418. },
  419. plaintextPass: []byte("testPassword"),
  420. expected: true,
  421. },
  422. {
  423. name: "Invalid plaintext password",
  424. user: User{
  425. AuthKey: "testAuthKey",
  426. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  427. },
  428. plaintextPass: []byte("wrongPassword"),
  429. expected: false,
  430. },
  431. {
  432. name: "Empty plaintext password",
  433. user: User{
  434. AuthKey: "testAuthKey",
  435. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  436. },
  437. plaintextPass: []byte(""),
  438. expected: false,
  439. },
  440. {
  441. name: "Empty stored password",
  442. user: User{
  443. AuthKey: "testAuthKey",
  444. WeakMD5Pass: []byte{},
  445. },
  446. plaintextPass: []byte("testPassword"),
  447. expected: false,
  448. },
  449. {
  450. name: "Password with special characters",
  451. user: User{
  452. AuthKey: "testAuthKey",
  453. WeakMD5Pass: wire.WeakMD5PasswordHash("test@123!", "testAuthKey"),
  454. },
  455. plaintextPass: []byte("test@123!"),
  456. expected: true,
  457. },
  458. }
  459. for _, tt := range tests {
  460. t.Run(tt.name, func(t *testing.T) {
  461. result := tt.user.ValidatePlaintextPass(tt.plaintextPass)
  462. assert.Equal(t, tt.expected, result)
  463. })
  464. }
  465. }
  466. func TestUser_ValidateRoastedKerberosPass(t *testing.T) {
  467. tests := []struct {
  468. name string
  469. user User
  470. roastedPass []byte
  471. expected bool
  472. }{
  473. {
  474. name: "Valid roasted Kerberos password",
  475. user: User{
  476. AuthKey: "testAuthKey",
  477. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  478. },
  479. roastedPass: wire.RoastKerberosPassword([]byte("testPassword")),
  480. expected: true,
  481. },
  482. {
  483. name: "Invalid roasted Kerberos password",
  484. user: User{
  485. AuthKey: "testAuthKey",
  486. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  487. },
  488. roastedPass: wire.RoastKerberosPassword([]byte("wrongPassword")),
  489. expected: false,
  490. },
  491. {
  492. name: "Empty roasted Kerberos password",
  493. user: User{
  494. AuthKey: "testAuthKey",
  495. WeakMD5Pass: wire.WeakMD5PasswordHash("testPassword", "testAuthKey"),
  496. },
  497. roastedPass: wire.RoastKerberosPassword([]byte("")),
  498. expected: false,
  499. },
  500. {
  501. name: "Empty stored password",
  502. user: User{
  503. AuthKey: "testAuthKey",
  504. WeakMD5Pass: []byte{},
  505. },
  506. roastedPass: wire.RoastKerberosPassword([]byte("testPassword")),
  507. expected: false,
  508. },
  509. }
  510. for _, tt := range tests {
  511. t.Run(tt.name, func(t *testing.T) {
  512. result := tt.user.ValidateRoastedKerberosPass(tt.roastedPass)
  513. assert.Equal(t, tt.expected, result)
  514. })
  515. }
  516. }