| <?php |
|
|
| use Illuminate\Http\Client\Request as HttpRequest; |
| use Illuminate\Support\Facades\Http; |
| use Inertia\Testing\AssertableInertia as Assert; |
|
|
| function studentAuthUserCookie(): string |
| { |
| return json_encode([ |
| 'email' => 'student@example.com', |
| 'id' => 11, |
| 'identity_number' => '123456', |
| 'name' => 'Alma', |
| 'role' => 'student', |
| 'status' => 'active', |
| ], JSON_THROW_ON_ERROR); |
| } |
|
|
| test('mahasiswa routes redirect guests to login', function () { |
| $this->get(route('mahasiswa')) |
| ->assertRedirect(route('login')); |
| }); |
|
|
| test('mahasiswa dashboard page can be rendered through laravel proxy', function () { |
| config(['services.rag.base_url' => 'https://rag.test/api/v1']); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/courses*' => Http::response([ |
| 'data' => [ |
| [ |
| 'description' => 'Materi struktur data', |
| 'id' => 101, |
| 'title' => 'Struktur Data', |
| ], |
| ], |
| 'limit' => 100, |
| 'page' => 1, |
| 'total' => 1, |
| ]), |
| 'https://rag.test/api/v1/chats/sessions*' => Http::response([ |
| 'data' => [ |
| [ |
| 'course_id' => '101', |
| 'created_at' => '2026-05-01T10:00:00Z', |
| 'message_count' => 2, |
| 'title' => 'Apa itu stack?', |
| 'updated_at' => '2026-05-01T10:00:00Z', |
| 'user_id' => 'student-1', |
| 'uuid_id' => 'session-1', |
| ], |
| ], |
| 'pagination' => [ |
| 'limit' => 20, |
| 'page' => 1, |
| 'total' => 1, |
| 'total_pages' => 1, |
| ], |
| ]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->get(route('mahasiswa')) |
| ->assertOk() |
| ->assertInertia(fn (Assert $page) => $page |
| ->component('mahasiswa') |
| ->where('backendMessage', null) |
| ->has('courses', 1) |
| ->where('courses.0.title', 'Struktur Data') |
| ->has('chatSessions', 1) |
| ->where('chatSessions.0.uuid_id', 'session-1') |
| ->where('selectedCourseId', '101')); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'GET' |
| && str_starts_with($request->url(), 'https://rag.test/api/v1/chats/sessions?') |
| && str_contains($request->url(), 'page=1') |
| && str_contains($request->url(), 'limit=20') |
| && $request->hasHeader('Authorization', 'Bearer token')); |
| }); |
|
|
| test('mahasiswa chat session can be created and initial message sent through laravel proxy', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'rest', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/chats/sessions/session-1/messages' => Http::response([ |
| 'content' => 'Stack adalah struktur data LIFO.', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'role' => 'assistant', |
| 'sources' => [], |
| 'uuid_id' => 'message-1', |
| ]), |
| 'https://rag.test/api/v1/chats/sessions' => Http::response([ |
| 'course_id' => '101', |
| 'created_at' => '2026-05-01T10:00:00Z', |
| 'message_count' => 0, |
| 'title' => 'Apa itu stack?', |
| 'updated_at' => '2026-05-01T10:00:00Z', |
| 'user_id' => 'student-1', |
| 'uuid_id' => 'session-1', |
| ], 201), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->from(route('mahasiswa')) |
| ->post(route('mahasiswa.sessions.store'), [ |
| 'course_id' => '101', |
| 'title' => 'Apa itu stack?', |
| ]) |
| ->assertRedirect(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->assertSessionMissing('sevima_raghub_pending_initial_chat.session-1') |
| ->assertSessionHasNoErrors(); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' |
| && $request->url() === 'https://rag.test/api/v1/chats/sessions' |
| && $request->hasHeader('Authorization', 'Bearer token') |
| && $request['course_id'] === '101' |
| && $request['title'] === 'Apa itu stack?'); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' |
| && $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1/messages' |
| && $request->hasHeader('Authorization', 'Bearer token') |
| && $request['content'] === 'Apa itu stack?'); |
| }); |
|
|
| test('mahasiswa chat detail page renders session and history from api', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'rest', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/courses*' => Http::response([ |
| 'data' => [ |
| [ |
| 'id' => 101, |
| 'title' => 'Struktur Data', |
| ], |
| ], |
| 'limit' => 100, |
| 'page' => 1, |
| 'total' => 1, |
| ]), |
| 'https://rag.test/api/v1/chats/sessions/session-1/messages' => Http::response([ |
| 'data' => [ |
| [ |
| 'content' => 'Apa itu stack?', |
| 'created_at' => '2026-05-01T10:00:00Z', |
| 'role' => 'user', |
| 'sources' => [], |
| 'uuid_id' => 'user-message-1', |
| ], |
| [ |
| 'content' => 'Stack adalah struktur data LIFO.', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'role' => 'assistant', |
| 'sources' => [], |
| 'uuid_id' => 'message-1', |
| ], |
| ], |
| ]), |
| 'https://rag.test/api/v1/chats/sessions/session-1' => Http::response([ |
| 'course_id' => '101', |
| 'created_at' => '2026-05-01T10:00:00Z', |
| 'message_count' => 2, |
| 'title' => 'Apa itu stack?', |
| 'updated_at' => '2026-05-01T10:01:00Z', |
| 'user_id' => 'student-1', |
| 'uuid_id' => 'session-1', |
| ]), |
| 'https://rag.test/api/v1/chats/sessions*' => Http::response([ |
| 'data' => [], |
| 'pagination' => [ |
| 'limit' => 20, |
| 'page' => 1, |
| 'total' => 0, |
| 'total_pages' => 0, |
| ], |
| ]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->get(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->assertOk() |
| ->assertInertia(fn (Assert $page) => $page |
| ->component('mahasiswa-chat') |
| ->where('sessionId', 'session-1') |
| ->where('currentSession.uuid_id', 'session-1') |
| ->has('messages', 2) |
| ->where('messages.0.role', 'user') |
| ->where('messages.1.role', 'assistant') |
| ->where('selectedCourseId', '101')); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1'); |
| Http::assertSent(fn (HttpRequest $request): bool => $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1/messages'); |
| }); |
|
|
| test('mahasiswa chat detail page can be rendered with direct session messages', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'direct', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/courses*' => Http::response([ |
| 'data' => [ |
| [ |
| 'id' => 101, |
| 'title' => 'Struktur Data', |
| ], |
| ], |
| 'limit' => 100, |
| 'page' => 1, |
| 'total' => 1, |
| ]), |
| 'https://rag.test/api/v1/chats/sessions/session-1/messages' => Http::response([ |
| 'data' => [], |
| ]), |
| 'https://rag.test/api/v1/chats/sessions/session-1' => Http::response([ |
| 'course_id' => '101', |
| 'created_at' => '2026-05-01T10:00:00Z', |
| 'message_count' => 0, |
| 'title' => 'Apa itu stack?', |
| 'updated_at' => '2026-05-01T10:00:00Z', |
| 'user_id' => 'student-1', |
| 'uuid_id' => 'session-1', |
| ]), |
| 'https://rag.test/api/v1/chats/sessions*' => Http::response([ |
| 'data' => [], |
| 'pagination' => [ |
| 'limit' => 20, |
| 'page' => 1, |
| 'total' => 0, |
| 'total_pages' => 0, |
| ], |
| ]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->withSession([ |
| 'sevima_raghub_direct_messages.session-1' => [ |
| [ |
| 'content' => 'Jawaban bot', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'role' => 'assistant', |
| 'sources' => [], |
| 'uuid_id' => 'message-1', |
| ], |
| ], |
| ]) |
| ->get(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->assertOk() |
| ->assertInertia(fn (Assert $page) => $page |
| ->component('mahasiswa-chat') |
| ->where('backendMessage', null) |
| ->where('sessionId', 'session-1') |
| ->where('currentSession.uuid_id', 'session-1') |
| ->has('messages', 1) |
| ->where('messages.0.content', 'Jawaban bot') |
| ->where('selectedCourseId', '101')); |
| }); |
|
|
| test('mahasiswa direct chat message can be sent through laravel proxy', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'direct', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/ai/query' => Http::response([ |
| 'content' => 'Stack adalah struktur data LIFO.', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'message_id' => 'message-1', |
| 'role' => 'assistant', |
| 'sources' => [], |
| ]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->from(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->post(route('mahasiswa.messages.store', ['sessionId' => 'session-1']), [ |
| 'content' => 'Apa itu stack?', |
| 'course_id' => '101', |
| ]) |
| ->assertRedirect(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->assertSessionHasNoErrors(); |
|
|
| $directMessages = session('sevima_raghub_direct_messages.session-1'); |
|
|
| expect($directMessages)->toHaveCount(2) |
| ->and($directMessages[0]['content'])->toBe('Apa itu stack?') |
| ->and($directMessages[1]['content'])->toBe('Stack adalah struktur data LIFO.'); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' |
| && $request->url() === 'https://rag.test/api/v1/ai/query' |
| && $request->hasHeader('Authorization', 'Bearer token') |
| && $request['course_id'] === '101' |
| && $request['prompt'] === 'Apa itu stack?'); |
| }); |
|
|
| test('mahasiswa rest chat message can be sent through laravel proxy', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'rest', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/chats/sessions/session-1/messages' => Http::response([ |
| 'content' => 'Stack adalah struktur data LIFO.', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'role' => 'assistant', |
| 'sources' => [], |
| 'uuid_id' => 'message-1', |
| ]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->from(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->post(route('mahasiswa.messages.store', ['sessionId' => 'session-1']), [ |
| 'content' => 'Apa itu stack?', |
| 'course_id' => '101', |
| ]) |
| ->assertRedirect(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->assertSessionHasNoErrors(); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' |
| && $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1/messages' |
| && $request->hasHeader('Authorization', 'Bearer token') |
| && $request['content'] === 'Apa itu stack?'); |
| }); |
|
|
| test('mahasiswa stream mode json chat message uses rest endpoint', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'stream', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/chats/sessions/session-1/messages' => Http::response([ |
| 'content' => 'Stack adalah struktur data LIFO.', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'role' => 'assistant', |
| 'sources' => [], |
| 'uuid_id' => 'message-1', |
| ]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->withHeaders(['Accept' => 'application/json']) |
| ->postJson(route('mahasiswa.messages.store', ['sessionId' => 'session-1']), [ |
| 'content' => 'Apa itu stack?', |
| 'course_id' => '101', |
| ]) |
| ->assertSuccessful() |
| ->assertJsonPath('assistantMessage.content', 'Stack adalah struktur data LIFO.'); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' |
| && $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1/messages' |
| && $request->hasHeader('Authorization', 'Bearer token') |
| && $request['content'] === 'Apa itu stack?'); |
| }); |
|
|
| test('mahasiswa stream chat message forwards backend server sent events', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'stream', |
| ]); |
|
|
| $streamBody = "data: {\"content\":\"Stack adalah struktur data LIFO.\"}\n\n"; |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/chats/sessions/session-1/stream' => Http::response( |
| $streamBody, |
| 200, |
| ['Content-Type' => 'text/event-stream'], |
| ), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->withHeaders(['Accept' => 'text/event-stream']) |
| ->post(route('mahasiswa.messages.store', ['sessionId' => 'session-1']), [ |
| 'content' => 'Apa itu stack?', |
| 'course_id' => '101', |
| ]) |
| ->assertSuccessful() |
| ->assertStreamed() |
| ->assertHeader('Content-Type', 'text/event-stream; charset=utf-8') |
| ->assertStreamedContent($streamBody); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' |
| && $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1/stream' |
| && $request->hasHeader('Authorization', 'Bearer token') |
| && $request->hasHeader('Accept', 'text/event-stream') |
| && $request['content'] === 'Apa itu stack?'); |
| }); |
|
|
| test('mahasiswa chat message uses backend error message', function () { |
| config([ |
| 'services.rag.base_url' => 'https://rag.test/api/v1', |
| 'services.rag.chat_mode' => 'direct', |
| ]); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/ai/query' => Http::response([ |
| 'message' => 'I do not know based on available materials.', |
| ], 422), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->from(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->post(route('mahasiswa.messages.store', ['sessionId' => 'session-1']), [ |
| 'content' => 'Apa itu stack?', |
| 'course_id' => '101', |
| ]) |
| ->assertRedirect(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->assertSessionHasErrors(['message' => 'I do not know based on available materials.']); |
| }); |
|
|
| test('mahasiswa chat session can be deleted through laravel proxy', function () { |
| config(['services.rag.base_url' => 'https://rag.test/api/v1']); |
|
|
| Http::fake([ |
| 'https://rag.test/api/v1/chats/sessions/session-1' => Http::response([]), |
| ]); |
|
|
| $this->withCookie('sevima_raghub_auth_token', 'token') |
| ->withCookie('sevima_raghub_auth_user', studentAuthUserCookie()) |
| ->withSession([ |
| 'sevima_raghub_direct_messages.session-1' => [ |
| [ |
| 'content' => 'Jawaban bot', |
| 'created_at' => '2026-05-01T10:01:00Z', |
| 'role' => 'assistant', |
| 'sources' => [], |
| 'uuid_id' => 'message-1', |
| ], |
| ], |
| ]) |
| ->from(route('mahasiswa.show', ['sessionId' => 'session-1'])) |
| ->delete(route('mahasiswa.destroy', ['sessionId' => 'session-1']), [ |
| 'active_session_id' => 'session-1', |
| ]) |
| ->assertRedirect(route('mahasiswa')) |
| ->assertSessionMissing('sevima_raghub_direct_messages.session-1') |
| ->assertSessionHasNoErrors(); |
|
|
| Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'DELETE' |
| && $request->url() === 'https://rag.test/api/v1/chats/sessions/session-1' |
| && $request->hasHeader('Authorization', 'Bearer token')); |
| }); |
|
|