2
0

SessionV3Test.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <?php
  2. use App\Models\UserInfo;
  3. use App\Services\AuthService;
  4. use Illuminate\Foundation\Testing\RefreshDatabase;
  5. uses(RefreshDatabase::class);
  6. /**
  7. * v3 登录与「我是谁」。这里断言的是形状而非契约(spectator 未安装)。
  8. *
  9. * 端点:
  10. * POST /api/v3/sessions 用户名或邮箱 + 密码 → token + 用户
  11. * GET /api/v3/me 当前用户(bearer)
  12. */
  13. /**
  14. * 建一个密码为 $password 的账号,返回 userid。
  15. */
  16. function makeAccount(string $username, string $password = 'secret-pw', ?string $role = null): string
  17. {
  18. $uid = makeStudio($username);
  19. UserInfo::where('userid', $uid)->update(['password' => md5($password), 'role' => $role]);
  20. return $uid;
  21. }
  22. it('signs in with a username and returns token and user in one response', function () {
  23. $uid = makeAccount('alice_1', 'secret-pw', json_encode(['basic']));
  24. $data = $this->postJson('/api/v3/sessions', ['login' => 'alice_1', 'password' => 'secret-pw'])
  25. ->assertCreated()
  26. ->json('data');
  27. expect($data)->toHaveKeys(['token', 'user'])
  28. ->and($data['user'])->toHaveKeys(['id', 'nickName', 'userName', 'realName', 'avatar', 'roles', 'email'])
  29. ->and($data['user']['id'])->toBe($uid)
  30. ->and($data['user']['roles'])->toBe(['basic'])
  31. ->and($data['user'])->not->toHaveKey('password');
  32. // token 能直接用来访问 v3 与 v2(两边互认)
  33. $this->getJson('/api/v3/me', ['Authorization' => 'Bearer '.$data['token']])
  34. ->assertOk()
  35. ->assertJsonPath('data.id', $uid);
  36. $this->getJson('/api/v2/auth/current', ['Authorization' => 'Bearer '.$data['token']])
  37. ->assertJsonPath('ok', true);
  38. });
  39. it('signs in with an email, case-insensitively', function () {
  40. makeAccount('bob_1');
  41. $this->postJson('/api/v3/sessions', ['login' => 'BOB_1@Example.Test', 'password' => 'secret-pw'])
  42. ->assertCreated()
  43. ->assertJsonPath('data.user.userName', 'bob_1');
  44. });
  45. it('does not trim the password', function () {
  46. makeAccount('carol_1', ' spaced ');
  47. $this->postJson('/api/v3/sessions', ['login' => 'carol_1', 'password' => ' spaced '])->assertCreated();
  48. $this->postJson('/api/v3/sessions', ['login' => 'carol_1', 'password' => 'spaced'])->assertStatus(422);
  49. });
  50. it('rejects a wrong password and an unknown account the same way', function () {
  51. makeAccount('dave_1');
  52. $wrong = $this->postJson('/api/v3/sessions', ['login' => 'dave_1', 'password' => 'nope'])
  53. ->assertStatus(422)
  54. ->assertJsonStructure(['errors' => ['login']])
  55. ->json('errors.login.0');
  56. $unknown = $this->postJson('/api/v3/sessions', ['login' => 'nobody_1', 'password' => 'nope'])
  57. ->assertStatus(422)
  58. ->json('errors.login.0');
  59. expect($wrong)->toBe($unknown);
  60. });
  61. it('requires login and password', function () {
  62. $this->postJson('/api/v3/sessions', [])
  63. ->assertStatus(422)
  64. ->assertJsonStructure(['errors' => ['login', 'password']]);
  65. });
  66. it('throttles guessing on one account', function () {
  67. makeAccount('erin_1');
  68. foreach (range(1, 5) as $_) {
  69. $this->postJson('/api/v3/sessions', ['login' => 'erin_1', 'password' => 'nope'])->assertStatus(422);
  70. }
  71. $this->postJson('/api/v3/sessions', ['login' => 'erin_1', 'password' => 'secret-pw'])->assertStatus(429);
  72. });
  73. it('reports the root admin as root', function () {
  74. $uid = makeAccount('root_1');
  75. config(['mint.admin.root_uuid' => $uid]);
  76. $this->postJson('/api/v3/sessions', ['login' => 'root_1', 'password' => 'secret-pw'])
  77. ->assertCreated()
  78. ->assertJsonPath('data.user.roles', ['root']);
  79. });
  80. it('returns an empty roles array when the account has none', function () {
  81. $uid = makeAccount('frank_1');
  82. $this->getJson('/api/v3/me', authHeader($uid))
  83. ->assertOk()
  84. ->assertJsonPath('data.roles', [])
  85. ->assertJsonPath('data.avatar', null)
  86. ->assertJsonMissingPath('data.token');
  87. });
  88. it('rejects /me without a valid token', function () {
  89. $this->getJson('/api/v3/me')->assertUnauthorized();
  90. $this->getJson('/api/v3/me', ['Authorization' => 'Bearer garbage'])->assertUnauthorized();
  91. });
  92. it('rejects /me when the account no longer exists', function () {
  93. $uid = makeAccount('gone_1');
  94. $token = AuthService::getUserToken($uid);
  95. UserInfo::where('userid', $uid)->delete();
  96. $this->getJson('/api/v3/me', ['Authorization' => 'Bearer '.$token])->assertUnauthorized();
  97. });