'lecturer@example.com', 'id' => 10, 'identity_number' => 'D12345', 'name' => 'Dosen', 'role' => 'lecturer', 'status' => 'active', ], JSON_THROW_ON_ERROR); } test('dosen routes redirect guests to login', function () { $this->get(route('dosen')) ->assertRedirect(route('login')); }); test('dosen dashboard page can be rendered', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses/101/documents' => Http::response([ 'data' => [ [ 'course_id' => '101', 'created_at' => '2026-05-01T10:00:00Z', 'file_size' => 1200, 'file_type' => 'pdf', 'filename' => 'materi.pdf', 'id' => 'doc-1', 'status' => 'ready', 'updated_at' => '2026-05-02T10:00:00Z', 'uploaded_by' => 'dosen-1', ], ], ]), 'https://rag.test/api/v1/courses*' => Http::response([ 'data' => [ [ 'description' => 'Materi dasar algoritma', 'id' => 101, 'title' => 'Algoritma & Pemrograman', ], ], 'limit' => 100, 'page' => 1, 'total' => 1, ]), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->get(route('dosen')) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('dosen') ->where('backendMessage', null) ->has('knowledgeBases', 1) ->where('knowledgeBases.0.id', '101') ->where('knowledgeBases.0.name', 'Algoritma & Pemrograman') ->where('knowledgeBases.0.documentCount', 1) ->where('knowledgeBases.0.failedDocumentCount', 0) ->where('knowledgeBases.0.updatedAt', '2026-05-02')); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'GET' && str_starts_with($request->url(), 'https://rag.test/api/v1/courses?') && $request->hasHeader('Authorization', 'Bearer token')); }); test('dosen settings page can be rendered', function () { $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->get(route('dosen.pengaturan')) ->assertOk() ->assertInertia(fn (Assert $page) => $page->component('dosen/pengaturan')); }); test('dosen course detail page can be rendered', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses/101/documents' => Http::response([ 'data' => [ [ 'course_id' => '101', 'created_at' => '2026-05-01T10:00:00Z', 'file_size' => 1200, 'file_type' => 'pdf', 'filename' => 'materi.pdf', 'id' => 'doc-1', 'status' => 'ready', 'updated_at' => '2026-05-02T10:00:00Z', 'uploaded_by' => 'dosen-1', ], ], ]), 'https://rag.test/api/v1/courses/101' => Http::response([ 'description' => 'Materi dasar algoritma', 'id' => 101, 'title' => 'Algoritma & Pemrograman', ]), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->get(route('dosen.show', ['courseId' => '101'])) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('dosen-detail') ->where('backendMessage', null) ->where('courseId', '101') ->where('course.id', 101) ->where('course.title', 'Algoritma & Pemrograman') ->has('documents', 1) ->where('documents.0.id', 'doc-1') ->where('documents.0.filename', 'materi.pdf')); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'GET' && $request->url() === 'https://rag.test/api/v1/courses/101' && $request->hasHeader('Authorization', 'Bearer token')); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'GET' && $request->url() === 'https://rag.test/api/v1/courses/101/documents' && $request->hasHeader('Authorization', 'Bearer token')); }); test('dosen course can be created through laravel proxy', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses' => Http::response([ 'description' => 'Materi dasar algoritma', 'id' => 101, 'title' => 'Algoritma & Pemrograman', ], 201), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen')) ->post(route('dosen.courses.store'), [ 'description' => 'Materi dasar algoritma', 'title' => 'Algoritma & Pemrograman', ]) ->assertRedirect(route('dosen')) ->assertSessionHasNoErrors(); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' && $request->url() === 'https://rag.test/api/v1/courses' && $request->hasHeader('Authorization', 'Bearer token') && $request['title'] === 'Algoritma & Pemrograman' && $request['description'] === 'Materi dasar algoritma'); }); test('dosen course create uses backend error message', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses' => Http::response([ 'message' => 'Course already exists.', ], 422), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen')) ->post(route('dosen.courses.store'), [ 'title' => 'Algoritma & Pemrograman', ]) ->assertRedirect(route('dosen')) ->assertSessionHasErrors(['course' => 'Course already exists.']); }); test('dosen course can be updated through laravel proxy', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses/101' => Http::response([ 'description' => 'Materi semester awal', 'id' => 101, 'title' => 'Algoritma Lanjut', ]), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen.show', ['courseId' => '101'])) ->put(route('dosen.update', ['courseId' => '101']), [ 'description' => 'Materi semester awal', 'title' => 'Algoritma Lanjut', ]) ->assertRedirect(route('dosen.show', ['courseId' => '101'])) ->assertSessionHasNoErrors(); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'PUT' && $request->url() === 'https://rag.test/api/v1/courses/101' && $request->hasHeader('Authorization', 'Bearer token') && $request['title'] === 'Algoritma Lanjut' && $request['description'] === 'Materi semester awal'); }); test('dosen course update uses backend error message', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses/101' => Http::response([ 'message' => 'Course title already exists.', ], 422), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen.show', ['courseId' => '101'])) ->put(route('dosen.update', ['courseId' => '101']), [ 'title' => 'Algoritma Lanjut', ]) ->assertRedirect(route('dosen.show', ['courseId' => '101'])) ->assertSessionHasErrors(['course' => 'Course title already exists.']); }); test('dosen course can be deleted through laravel proxy', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses/101' => Http::response([]), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen.show', ['courseId' => '101'])) ->delete(route('dosen.destroy', ['courseId' => '101'])) ->assertRedirect(route('dosen')) ->assertSessionHasNoErrors(); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'DELETE' && $request->url() === 'https://rag.test/api/v1/courses/101' && $request->hasHeader('Authorization', 'Bearer token')); }); test('dosen course delete uses backend error message', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/courses/101' => Http::response([ 'message' => 'Course still has active documents.', ], 422), ]); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen.show', ['courseId' => '101'])) ->delete(route('dosen.destroy', ['courseId' => '101'])) ->assertRedirect(route('dosen.show', ['courseId' => '101'])) ->assertSessionHasErrors(['course' => 'Course still has active documents.']); }); test('dosen document can be uploaded through laravel proxy', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/documents/upload' => Http::response([ 'document_id' => 'doc-1', 'filename' => 'materi.pdf', 'message' => 'File accepted.', 'status' => 'processing', ], 202), ]); $file = UploadedFile::fake()->create('materi.pdf', 100, 'application/pdf'); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen.show', ['courseId' => '101'])) ->post(route('dosen.documents.store', ['courseId' => '101']), [ 'file' => $file, ]) ->assertRedirect(route('dosen.show', ['courseId' => '101'])) ->assertSessionHasNoErrors(); Http::assertSent(fn (HttpRequest $request): bool => $request->method() === 'POST' && $request->url() === 'https://rag.test/api/v1/documents/upload' && $request->hasHeader('Authorization', 'Bearer token') && $request->isMultipart() && $request->hasFile('file', filename: 'materi.pdf') && collect($request->data())->contains( fn (array $part): bool => ($part['name'] ?? null) === 'course_id' && ($part['contents'] ?? null) === '101', )); }); test('dosen document upload uses backend error message', function () { config(['services.rag.base_url' => 'https://rag.test/api/v1']); Http::fake([ 'https://rag.test/api/v1/documents/upload' => Http::response([ 'detail' => [ ['msg' => 'Unsupported file type.'], ], ], 422), ]); $file = UploadedFile::fake()->create('materi.pdf', 100, 'application/pdf'); $this->withCookie('sevima_raghub_auth_token', 'token') ->withCookie('sevima_raghub_auth_user', lecturerAuthUserCookie()) ->from(route('dosen.show', ['courseId' => '101'])) ->post(route('dosen.documents.store', ['courseId' => '101']), [ 'file' => $file, ]) ->assertRedirect(route('dosen.show', ['courseId' => '101'])) ->assertSessionHasErrors(['file' => 'Unsupported file type.']); });