getJson('/api/v1/admin/ping')->assertUnauthorized() ->assertJsonPath('code', ErrorCode::AdminUnauthenticated->value); }); test('admin login returns bearer token when captcha passes validation', function () { AdminUser::query()->create([ 'username' => 'tester', 'name' => '测试昵称', 'email' => null, 'password' => 'secret-strong', 'status' => 0, ]); $mock = Mockery::mock(AdminCaptchaService::class); $mock->shouldReceive('verify')->once()->andReturn(true); $this->instance(AdminCaptchaService::class, $mock); $resp = $this->postJson('/api/v1/admin/auth/login', [ 'account' => 'Tester', 'password' => 'secret-strong', 'captcha_key' => (string) Str::uuid(), 'captcha_code' => 'xwz2', ]); $resp->assertOk() ->assertJsonPath('code', 0) ->assertJsonPath('data.admin.username', 'tester') ->assertJsonPath('data.admin.nickname', '测试昵称') ->assertJsonStructure(['data' => ['token', 'token_type', 'admin' => ['id', 'username', 'nickname', 'email']]]); $token = $resp->json('data.token'); expect($token)->not->toBeNull(); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/ping') ->assertOk() ->assertJsonPath('data.scope', 'admin'); }); test('admin captcha exposes key and image base64', function () { $resp = $this->getJson('/api/v1/admin/auth/captcha'); $resp->assertOk() ->assertJsonPath('code', 0); $data = $resp->json('data'); expect($data)->toBeArray() ->and(Str::isUuid((string) $data['captcha_key']))->toBeTrue() ->and((string) $data['image_base64'])->not->toBe(''); }); test('login rejects wrong password with masked message', function () { AdminUser::query()->create([ 'username' => 'bad_tester', 'name' => 'X', 'email' => null, 'password' => 'right-only', 'status' => 0, ]); $mock = Mockery::mock(AdminCaptchaService::class); $mock->shouldReceive('verify')->andReturn(true); $this->instance(AdminCaptchaService::class, $mock); $this->postJson('/api/v1/admin/auth/login', [ 'account' => 'bad_tester', 'password' => 'wrong-password', 'captcha_key' => (string) Str::uuid(), 'captcha_code' => 'aaaa', ])->assertUnauthorized() ->assertJsonPath('code', ErrorCode::AdminCredentialsInvalid->value); });