fetchUser(static function ($query): void { $query->where('status', 1); }); } if (!$user) { $this->markTestSkipped('没有可用的活跃用户,无法测试'); } $auth = make(AuthManager::class); return $auth->guard('jwt')->login($user); } protected function authHeaders(?User $user = null): array { return ['Authorization' => 'Bearer ' . $this->getAuthToken($user)]; } protected function fetchUser(?callable $callback = null): ?User { if (\Swoole\Coroutine::getCid() > 0) { $query = User::query(); if ($callback !== null) { $callback($query); } return $query->first(); } $user = null; \Swoole\Coroutine\run(static function () use ($callback, &$user): void { $query = User::query(); if ($callback !== null) { $callback($query); } $user = $query->first(); }); return $user; } protected function createTestUser(array $overrides = []): User { $suffix = bin2hex(random_bytes(4)); return User::query()->create(array_merge([ 'username' => 'auth_test_' . $suffix, 'password' => 'Pass_' . $suffix, 'email' => 'auth_test_' . $suffix . '@example.com', 'status' => 1, ], $overrides)); } // ========== 注册接口参数校验 ========== public function test_register_success(): void { $suffix = bin2hex(random_bytes(4)); $response = $this->post('/api/v1/register', [ 'username' => 'reg_' . $suffix, 'password' => 'Pass_' . $suffix, 'email' => 'reg_' . $suffix . '@example.com', ]); $response->assertStatus(200); $response->assertJsonPath('code', 0); $response->assertJsonPath('data.username', 'reg_' . $suffix); } public function test_register_missing_username_returns_400(): void { $response = $this->post('/api/v1/register', [ 'password' => 'Pass_1234', 'email' => 'test@example.com', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_register_short_username_returns_400(): void { $response = $this->post('/api/v1/register', [ 'username' => 'ab', 'password' => 'Pass_1234', 'email' => 'test_short@example.com', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_register_missing_password_returns_400(): void { $suffix = bin2hex(random_bytes(4)); $response = $this->post('/api/v1/register', [ 'username' => 'reg_' . $suffix, 'email' => 'reg_' . $suffix . '@example.com', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_register_short_password_returns_400(): void { $suffix = bin2hex(random_bytes(4)); $response = $this->post('/api/v1/register', [ 'username' => 'reg_' . $suffix, 'password' => '12345', 'email' => 'reg_' . $suffix . '@example.com', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_register_invalid_email_returns_400(): void { $suffix = bin2hex(random_bytes(4)); $response = $this->post('/api/v1/register', [ 'username' => 'reg_' . $suffix, 'password' => 'Pass_1234', 'email' => 'not-an-email', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_register_duplicate_username_returns_400(): void { $user = $this->createTestUser(); $response = $this->post('/api/v1/register', [ 'username' => $user->username, 'password' => 'Pass_1234', 'email' => 'dup_' . bin2hex(random_bytes(4)) . '@example.com', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } // ========== 登录接口参数校验 ========== public function test_login_success(): void { $password = 'Login_test_pass'; $user = $this->createTestUser(['password' => $password]); $response = $this->post('/api/v1/login', [ 'username' => $user->username, 'password' => $password, ]); $response->assertStatus(200); $response->assertJsonPath('code', 0); $response->assertJsonStructure([ 'data' => ['access_token', 'refresh_token', 'token_type', 'expires_in', 'user'], ]); } public function test_login_missing_username_returns_400(): void { $response = $this->post('/api/v1/login', [ 'password' => 'Pass_1234', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_login_missing_password_returns_400(): void { $response = $this->post('/api/v1/login', [ 'username' => 'some_user', ]); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_login_wrong_password_returns_401(): void { $user = $this->createTestUser(); $response = $this->post('/api/v1/login', [ 'username' => $user->username, 'password' => 'wrong_password_here', ]); $response->assertStatus(401); $response->assertJsonPath('code', 401); } public function test_login_disabled_user_returns_403(): void { $password = 'Disabled_pass'; $user = $this->createTestUser(['password' => $password, 'status' => 0]); $response = $this->post('/api/v1/login', [ 'username' => $user->username, 'password' => $password, ]); $response->assertStatus(403); $response->assertJsonPath('code', 403); } // ========== 密码修改接口 ========== public function test_change_password_success(): void { $old_password = 'OldPass_1234'; $new_password = 'NewPass_5678'; $user = $this->createTestUser(['password' => $old_password]); $response = $this->put('/api/v1/me/password', [ 'old_password' => $old_password, 'new_password' => $new_password, ], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); // 验证 refresh_token 已被清除 $user->refresh(); $this->assertNull($user->refresh_token); $this->assertNull($user->refresh_token_expires_at); } public function test_change_password_clears_refresh_token(): void { $old_password = 'OldPass_clear'; $user = $this->createTestUser(['password' => $old_password]); // 先登录获取 refresh_token $this->post('/api/v1/login', [ 'username' => $user->username, 'password' => $old_password, ]); $user->refresh(); $this->assertNotNull($user->refresh_token); // 修改密码 $response = $this->put('/api/v1/me/password', [ 'old_password' => $old_password, 'new_password' => 'NewPass_clear', ], $this->authHeaders($user)); $response->assertStatus(200); $user->refresh(); $this->assertNull($user->refresh_token); } public function test_change_password_wrong_old_password_returns_400(): void { $user = $this->createTestUser(['password' => 'CorrectOld_1']); $response = $this->put('/api/v1/me/password', [ 'old_password' => 'WrongOld_pass', 'new_password' => 'NewPass_5678', ], $this->authHeaders($user)); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_change_password_short_new_password_returns_400(): void { $old_password = 'OldPass_short'; $user = $this->createTestUser(['password' => $old_password]); $response = $this->put('/api/v1/me/password', [ 'old_password' => $old_password, 'new_password' => '12345', ], $this->authHeaders($user)); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_change_password_missing_old_password_returns_400(): void { $user = $this->createTestUser(); $response = $this->put('/api/v1/me/password', [ 'new_password' => 'NewPass_5678', ], $this->authHeaders($user)); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_change_password_without_token_returns_401(): void { $response = $this->put('/api/v1/me/password', [ 'old_password' => 'old', 'new_password' => 'new_pass', ]); $response->assertStatus(401); } // ========== 个人信息更新接口 ========== public function test_update_profile_email(): void { $user = $this->createTestUser(); $new_email = 'profile_' . bin2hex(random_bytes(4)) . '@example.com'; $response = $this->put('/api/v1/me/profile', [ 'email' => $new_email, ], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); $response->assertJsonPath('data.email', $new_email); $user->refresh(); $this->assertSame($new_email, $user->email); } public function test_update_profile_ext(): void { $user = $this->createTestUser(); $ext = ['nickname' => 'TestNick', 'theme' => 'dark']; $response = $this->put('/api/v1/me/profile', [ 'ext' => $ext, ], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); $response->assertJsonPath('data.ext.nickname', 'TestNick'); } public function test_update_profile_invalid_email_returns_400(): void { $user = $this->createTestUser(); $response = $this->put('/api/v1/me/profile', [ 'email' => 'not-an-email', ], $this->authHeaders($user)); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_update_profile_duplicate_email_returns_400(): void { $existing = $this->createTestUser(); $user = $this->createTestUser(); $response = $this->put('/api/v1/me/profile', [ 'email' => $existing->email, ], $this->authHeaders($user)); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_update_profile_no_fields_returns_400(): void { $user = $this->createTestUser(); $response = $this->put('/api/v1/me/profile', [], $this->authHeaders($user)); $response->assertStatus(400); $response->assertJsonPath('code', 400); } public function test_update_profile_without_token_returns_401(): void { $response = $this->put('/api/v1/me/profile', [ 'email' => 'test@example.com', ]); $response->assertStatus(401); } // ========== 现有接口测试 ========== public function test_me_returns_user_info(): void { $user = $this->createTestUser(); $response = $this->get('/api/v1/me', [], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); $response->assertJsonPath('data.id', $user->id); $response->assertJsonPath('data.username', $user->username); $response->assertJsonStructure([ 'data' => ['id', 'username', 'email', 'status', 'ext', 'created_at'], ]); } public function test_me_without_token_returns_401(): void { $response = $this->get('/api/v1/me'); $response->assertStatus(401); } public function test_logout_clears_refresh_token(): void { $password = 'LogoutTest_1'; $user = $this->createTestUser(['password' => $password]); // 先登录获取 refresh_token $this->post('/api/v1/login', [ 'username' => $user->username, 'password' => $password, ]); $user->refresh(); $this->assertNotNull($user->refresh_token); // 退出登录 $response = $this->get('/api/v1/logout', [], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); $user->refresh(); $this->assertNull($user->refresh_token); } }