main: 用户管理和会话功能初始实现
- 添加用户管理功能的测试,包括创建、更新、停用、激活用户及用户登录 JWT 测试 - 提供用户管理相关的请求验证类与控制器 - 引入 CORS 配置信息,支持跨域请求 - 添加数据库播种器以便创建根用户 - 配置 API 默认使用 JWT 认证 - 添加聊天会话和消息的模型、迁移文件及关联功能
This commit is contained in:
108
tests/Feature/ChatSessionTest.php
Normal file
108
tests/Feature/ChatSessionTest.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\ChatSessionStatus;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ChatSessionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function authHeader(User $user): array
|
||||
{
|
||||
return ['Authorization' => 'Bearer '.JWTAuth::fromUser($user)];
|
||||
}
|
||||
|
||||
public function test_append_messages_sequential_seq_and_last_seq(): void
|
||||
{
|
||||
$service = app(ChatService::class);
|
||||
$session = $service->createSession('Test Session');
|
||||
|
||||
$messages = collect(range(1, 20))->map(function ($i) use ($service, $session) {
|
||||
return $service->appendMessage([
|
||||
'session_id' => $session->session_id,
|
||||
'role' => 'USER',
|
||||
'type' => 'user.prompt',
|
||||
'content' => 'msg '.$i,
|
||||
]);
|
||||
});
|
||||
|
||||
$seqs = $messages->pluck('seq')->all();
|
||||
$this->assertEquals(range(1, 20), $seqs);
|
||||
|
||||
$session->refresh();
|
||||
$this->assertEquals(20, $session->last_seq);
|
||||
}
|
||||
|
||||
public function test_dedupe_returns_existing_message(): void
|
||||
{
|
||||
$service = app(ChatService::class);
|
||||
$session = $service->createSession('Test Session');
|
||||
|
||||
$first = $service->appendMessage([
|
||||
'session_id' => $session->session_id,
|
||||
'role' => 'USER',
|
||||
'type' => 'user.prompt',
|
||||
'content' => 'hello',
|
||||
'dedupe_key' => 'k1',
|
||||
]);
|
||||
|
||||
$second = $service->appendMessage([
|
||||
'session_id' => $session->session_id,
|
||||
'role' => 'USER',
|
||||
'type' => 'user.prompt',
|
||||
'content' => 'hello again',
|
||||
'dedupe_key' => 'k1',
|
||||
]);
|
||||
|
||||
$this->assertEquals($first->message_id, $second->message_id);
|
||||
$this->assertEquals($first->seq, $second->seq);
|
||||
$this->assertCount(1, $session->messages()->get());
|
||||
}
|
||||
|
||||
public function test_closed_session_blocks_append_except_whitelisted(): void
|
||||
{
|
||||
$service = app(ChatService::class);
|
||||
$session = $service->createSession('Test Session');
|
||||
$session->update(['status' => ChatSessionStatus::CLOSED]);
|
||||
|
||||
$this->expectExceptionMessage('Session is closed');
|
||||
$service->appendMessage([
|
||||
'session_id' => $session->session_id,
|
||||
'role' => 'USER',
|
||||
'type' => 'user.prompt',
|
||||
'content' => 'blocked',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_http_append_and_list_messages(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$headers = $this->authHeader($user);
|
||||
|
||||
$createSession = $this->withHeaders($headers)->postJson('/api/sessions', [
|
||||
'session_name' => 'API Session',
|
||||
])->assertCreated();
|
||||
|
||||
$sessionId = $createSession->json('session_id');
|
||||
|
||||
$append = $this->withHeaders($headers)->postJson("/api/sessions/{$sessionId}/messages", [
|
||||
'role' => 'USER',
|
||||
'type' => 'user.prompt',
|
||||
'content' => 'hello api',
|
||||
])->assertCreated();
|
||||
|
||||
$this->assertEquals(1, $append->json('data.seq'));
|
||||
|
||||
$list = $this->withHeaders($headers)->getJson("/api/sessions/{$sessionId}/messages?after_seq=0&limit=10")
|
||||
->assertOk();
|
||||
|
||||
$this->assertEquals(1, $list->json('data.0.seq'));
|
||||
$this->assertEquals('hello api', $list->json('data.0.content'));
|
||||
}
|
||||
}
|
||||
21
tests/Feature/CorsTest.php
Normal file
21
tests/Feature/CorsTest.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CorsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_allows_configured_origin(): void
|
||||
{
|
||||
$origin = 'http://localhost:5173';
|
||||
|
||||
$response = $this->withHeaders(['Origin' => $origin])->get('/api/health');
|
||||
|
||||
$response->assertOk();
|
||||
$this->assertEquals($origin, $response->headers->get('Access-Control-Allow-Origin'));
|
||||
}
|
||||
}
|
||||
161
tests/Feature/UserManagementTest.php
Normal file
161
tests/Feature/UserManagementTest.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserManagementTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function authHeader(User $user): array
|
||||
{
|
||||
return ['Authorization' => 'Bearer '.JWTAuth::fromUser($user)];
|
||||
}
|
||||
|
||||
public function test_can_create_user(): void
|
||||
{
|
||||
$admin = User::factory()->create();
|
||||
|
||||
$response = $this->withHeaders($this->authHeader($admin))->postJson('/api/users', [
|
||||
'name' => 'Alice',
|
||||
'email' => 'alice@example.com',
|
||||
'password' => 'Password123',
|
||||
]);
|
||||
|
||||
$response->assertCreated()->assertJsonFragment([
|
||||
'name' => 'Alice',
|
||||
'email' => 'alice@example.com',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'email' => 'alice@example.com',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$user = User::whereEmail('alice@example.com')->first();
|
||||
$this->assertNotNull($user);
|
||||
$this->assertTrue(Hash::check('Password123', $user->password));
|
||||
}
|
||||
|
||||
public function test_can_list_users(): void
|
||||
{
|
||||
$admin = User::factory()->create(['name' => 'Admin']);
|
||||
$activeUser = User::factory()->create(['name' => 'Active User', 'email' => 'active@example.com']);
|
||||
$inactiveUser = User::factory()->create(['name' => 'Inactive User', 'email' => 'inactive@example.com', 'is_active' => false]);
|
||||
|
||||
$response = $this->withHeaders($this->authHeader($admin))->getJson('/api/users');
|
||||
|
||||
$response->assertOk()->assertJsonStructure([
|
||||
'data' => [
|
||||
['id', 'name', 'email', 'is_active', 'created_at', 'updated_at'],
|
||||
],
|
||||
'links',
|
||||
'meta',
|
||||
]);
|
||||
|
||||
$response->assertJsonCount(3, 'data');
|
||||
$response->assertJsonFragment(['email' => $activeUser->email, 'is_active' => true]);
|
||||
$response->assertJsonFragment(['email' => $inactiveUser->email, 'is_active' => false]);
|
||||
}
|
||||
|
||||
public function test_can_update_user(): void
|
||||
{
|
||||
$admin = User::factory()->create();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->withHeaders($this->authHeader($admin))->putJson("/api/users/{$user->id}", [
|
||||
'name' => 'Updated User',
|
||||
'email' => 'updated@example.com',
|
||||
'password' => 'NewPassword123',
|
||||
]);
|
||||
|
||||
$response->assertOk()->assertJsonFragment([
|
||||
'id' => $user->id,
|
||||
'name' => 'Updated User',
|
||||
'email' => 'updated@example.com',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$user->refresh();
|
||||
$this->assertEquals('Updated User', $user->name);
|
||||
$this->assertEquals('updated@example.com', $user->email);
|
||||
$this->assertTrue(Hash::check('NewPassword123', $user->password));
|
||||
}
|
||||
|
||||
public function test_can_deactivate_and_activate_user(): void
|
||||
{
|
||||
$admin = User::factory()->create();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$deactivate = $this->withHeaders($this->authHeader($admin))->postJson("/api/users/{$user->id}/deactivate");
|
||||
$deactivate->assertOk()->assertJsonFragment(['is_active' => false]);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
$activate = $this->withHeaders($this->authHeader($admin))->postJson("/api/users/{$user->id}/activate");
|
||||
$activate->assertOk()->assertJsonFragment(['is_active' => true]);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_login_and_receive_jwt(): void
|
||||
{
|
||||
$password = 'Password123';
|
||||
$user = User::factory()->create([
|
||||
'password' => Hash::make($password),
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/api/login', [
|
||||
'email' => $user->email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$response->assertOk()->assertJsonStructure([
|
||||
'token',
|
||||
'token_type',
|
||||
'expires_in',
|
||||
'user' => ['id', 'name', 'email', 'is_active'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_inactive_user_cannot_login(): void
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'password' => Hash::make('Password123'),
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/api/login', [
|
||||
'email' => $user->email,
|
||||
'password' => 'Password123',
|
||||
]);
|
||||
|
||||
$response->assertForbidden()->assertJson([
|
||||
'message' => '用户已停用',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_database_seeder_creates_root_user(): void
|
||||
{
|
||||
$this->seed();
|
||||
|
||||
$root = User::whereEmail('root@example.com')->first();
|
||||
|
||||
$this->assertNotNull($root);
|
||||
$this->assertEquals('root', $root->name);
|
||||
$this->assertTrue($root->is_active);
|
||||
$this->assertTrue(Hash::check('Root@123456', $root->password));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user