main: 增强会话功能,支持消息管理和接口文档

- 添加 `last_message_id` 字段至 `chat_sessions` 表,更新其关联索引
- 实现会话更新接口,支持修改名称与状态并添加验证逻辑
- 增加会话列表接口,支持状态过滤与关键字查询
- 提供会话和消息相关的资源类和请求验证类
- 扩展 `ChatService` 服务层逻辑以处理会话更新和消息附加
- 编写测试用例以验证新功能的正确性
- 增加接口文档及 OpenAPI 规范文件,覆盖新增功能
- 更新数据库播种器,添加默认用户
This commit is contained in:
2025-12-14 20:20:27 +08:00
parent c6d6534b63
commit 6356baacc0
14 changed files with 852 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ namespace Tests\Feature;
use App\Enums\ChatSessionStatus;
use App\Models\User;
use App\Services\ChatService;
use Illuminate\Support\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
use Tests\TestCase;
@@ -89,7 +90,7 @@ class ChatSessionTest extends TestCase
'session_name' => 'API Session',
])->assertCreated();
$sessionId = $createSession->json('session_id');
$sessionId = $createSession->json('data.session_id');
$append = $this->withHeaders($headers)->postJson("/api/sessions/{$sessionId}/messages", [
'role' => 'USER',
@@ -105,4 +106,116 @@ class ChatSessionTest extends TestCase
$this->assertEquals(1, $list->json('data.0.seq'));
$this->assertEquals('hello api', $list->json('data.0.content'));
}
public function test_session_list_sorted_and_last_message_preview(): void
{
$user = User::factory()->create();
$headers = $this->authHeader($user);
$service = app(ChatService::class);
Carbon::setTestNow('2025-01-01 00:00:00');
$s1 = $service->createSession('First');
$service->appendMessage([
'session_id' => $s1->session_id,
'role' => 'USER',
'type' => 'user.prompt',
'content' => 'hello first',
]);
Carbon::setTestNow('2025-01-01 00:00:10');
$s2 = $service->createSession('Second');
$service->appendMessage([
'session_id' => $s2->session_id,
'role' => 'USER',
'type' => 'user.prompt',
'content' => 'hello second',
]);
Carbon::setTestNow();
$resp = $this->withHeaders($headers)->getJson('/api/sessions?per_page=10')
->assertOk();
$this->assertEquals($s2->session_id, $resp->json('data.0.session_id'));
$this->assertEquals('hello second', $resp->json('data.0.last_message_preview'));
$this->assertEquals('hello first', $resp->json('data.1.last_message_preview'));
$this->assertNotNull($resp->json('data.0.last_message_at'));
}
public function test_session_list_filters_status_and_query(): void
{
$user = User::factory()->create();
$headers = $this->authHeader($user);
$service = app(ChatService::class);
$open = $service->createSession('Alpha');
$locked = $service->createSession('Beta');
$locked->update(['status' => ChatSessionStatus::LOCKED]);
$closed = $service->createSession('Gamma');
$closed->update(['status' => ChatSessionStatus::CLOSED]);
$respStatus = $this->withHeaders($headers)->getJson('/api/sessions?status=LOCKED')
->assertOk();
$this->assertCount(1, $respStatus->json('data'));
$this->assertEquals($locked->session_id, $respStatus->json('data.0.session_id'));
$respQuery = $this->withHeaders($headers)->getJson('/api/sessions?q=Alpha')
->assertOk();
$this->assertCount(1, $respQuery->json('data'));
$this->assertEquals($open->session_id, $respQuery->json('data.0.session_id'));
}
public function test_patch_updates_session_name(): void
{
$user = User::factory()->create();
$headers = $this->authHeader($user);
$service = app(ChatService::class);
$session = $service->createSession('Old Name');
$resp = $this->withHeaders($headers)->patchJson("/api/sessions/{$session->session_id}", [
'session_name' => 'New Name',
])->assertOk();
$this->assertEquals('New Name', $resp->json('data.session_name'));
}
public function test_patch_updates_status_transitions(): void
{
$user = User::factory()->create();
$headers = $this->authHeader($user);
$service = app(ChatService::class);
$session = $service->createSession('Status Session');
$this->withHeaders($headers)->patchJson("/api/sessions/{$session->session_id}", [
'status' => ChatSessionStatus::LOCKED,
])->assertOk()->assertJsonFragment(['status' => ChatSessionStatus::LOCKED]);
$this->withHeaders($headers)->patchJson("/api/sessions/{$session->session_id}", [
'status' => ChatSessionStatus::OPEN,
])->assertOk()->assertJsonFragment(['status' => ChatSessionStatus::OPEN]);
}
public function test_closed_session_cannot_reopen(): void
{
$user = User::factory()->create();
$headers = $this->authHeader($user);
$service = app(ChatService::class);
$session = $service->createSession('Closed Session');
$session->update(['status' => ChatSessionStatus::CLOSED]);
$this->withHeaders($headers)->patchJson("/api/sessions/{$session->session_id}", [
'status' => ChatSessionStatus::OPEN,
])->assertStatus(403);
}
public function test_empty_patch_rejected(): void
{
$user = User::factory()->create();
$headers = $this->authHeader($user);
$service = app(ChatService::class);
$session = $service->createSession('Patch Empty');
$this->withHeaders($headers)->patchJson("/api/sessions/{$session->session_id}", [])
->assertStatus(422);
}
}