feat: API wali murid (Sanctum) + verifikasi admin
- Pasang laravel/sanctum, routes/api.php (prefix api/v1, 10 endpoint) - Migrasi: parent_name di students, parent_students (pivot wali↔anak), parent_devices (token FCM), permission parents-verify - Auth wali: registrasi NISN+HP (auto-verify jika HP cocok data siswa, pending jika tidak) + login token Sanctum - Endpoint: me, logout, students, link anak, violations, sp-letters, notifications, register device - Halaman admin Verifikasi Wali (approve/reject) + menu sidebar - Sync kesiswaan: tarik data orang tua (student_parents) ke students - 8 feature test baru (ParentApiTest) — total suite 39 test hijau
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\ParentStudent;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use App\Models\Violation;
|
||||
use App\Models\ViolationType;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ParentApiTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function makeStudent(array $overrides = []): Student
|
||||
{
|
||||
return Student::create(array_merge([
|
||||
'nisn' => '0099990001',
|
||||
'student_number' => 'TEST/001',
|
||||
'full_name' => 'SISWA TEST',
|
||||
'class_name' => 'X TKJ 1',
|
||||
'class_level' => 'X',
|
||||
'department_name' => 'TKJ',
|
||||
'status' => 'active',
|
||||
'is_active' => true,
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
public function test_register_auto_verifies_when_phone_matches(): void
|
||||
{
|
||||
$this->makeStudent([
|
||||
'nisn' => '0099990001',
|
||||
'parent_phone' => '081234567890',
|
||||
'parent_name' => 'Orang Tua Test',
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/api/v1/parent/register', [
|
||||
'nisn' => '0099990001',
|
||||
'parent_phone' => '081234567890',
|
||||
'name' => 'Wali Test',
|
||||
'password' => 'rahasia123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201)
|
||||
->assertJsonPath('needs_verification', false)
|
||||
->assertJsonPath('students.0.link_status', 'active')
|
||||
->assertJsonStructure(['token', 'user' => ['id', 'name', 'username']]);
|
||||
|
||||
$this->assertDatabaseHas('parent_students', [
|
||||
'status' => 'active',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_register_creates_pending_when_phone_not_match(): void
|
||||
{
|
||||
$this->makeStudent(['nisn' => '0099990001']); // parent_phone null
|
||||
|
||||
$response = $this->postJson('/api/v1/parent/register', [
|
||||
'nisn' => '0099990001',
|
||||
'parent_phone' => '081298765432',
|
||||
'name' => 'Wali Test',
|
||||
'password' => 'rahasia123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201)
|
||||
->assertJsonPath('needs_verification', true)
|
||||
->assertJsonPath('students.0.link_status', 'pending');
|
||||
|
||||
$this->assertDatabaseHas('parent_students', [
|
||||
'status' => 'pending',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_register_rejects_unknown_nisn(): void
|
||||
{
|
||||
$response = $this->postJson('/api/v1/parent/register', [
|
||||
'nisn' => '0000000000',
|
||||
'parent_phone' => '081298765432',
|
||||
'name' => 'Wali Test',
|
||||
'password' => 'rahasia123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonValidationErrors('nisn');
|
||||
}
|
||||
|
||||
public function test_login_and_me(): void
|
||||
{
|
||||
$this->makeStudent(['nisn' => '0099990001']);
|
||||
$this->postJson('/api/v1/parent/register', [
|
||||
'nisn' => '0099990001',
|
||||
'parent_phone' => '081298765432',
|
||||
'name' => 'Wali Test',
|
||||
'password' => 'rahasia123',
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/api/v1/parent/login', [
|
||||
'username' => '0099990001',
|
||||
'password' => 'rahasia123',
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure(['token', 'user' => ['username'], 'students']);
|
||||
|
||||
$token = $response->json('token');
|
||||
|
||||
$this->withToken($token)
|
||||
->getJson('/api/v1/parent/me')
|
||||
->assertOk()
|
||||
->assertJsonPath('user.username', '0099990001');
|
||||
}
|
||||
|
||||
public function test_pending_link_cannot_access_violations(): void
|
||||
{
|
||||
$student = $this->makeStudent(['nisn' => '0099990001']);
|
||||
$user = User::factory()->create(['username' => '0099990001', 'role' => 'parent', 'roles' => ['parent']]);
|
||||
ParentStudent::create([
|
||||
'user_id' => $user->id,
|
||||
'student_id' => $student->id,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->getJson("/api/v1/parent/students/{$student->id}/violations")
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_active_link_can_access_violations(): void
|
||||
{
|
||||
$student = $this->makeStudent(['nisn' => '0099990001']);
|
||||
$user = User::factory()->create(['username' => '0099990001', 'role' => 'parent', 'roles' => ['parent']]);
|
||||
ParentStudent::create([
|
||||
'user_id' => $user->id,
|
||||
'student_id' => $student->id,
|
||||
'status' => 'active',
|
||||
'verified_at' => now(),
|
||||
]);
|
||||
|
||||
$category = \App\Models\ViolationCategory::create([
|
||||
'name' => 'Kedisiplinan',
|
||||
'slug' => 'kedisiplinan',
|
||||
]);
|
||||
|
||||
$type = ViolationType::create([
|
||||
'category_id' => $category->id,
|
||||
'name' => 'Terlambat',
|
||||
'slug' => 'terlambat',
|
||||
'points' => 5,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
Violation::create([
|
||||
'student_id' => $student->id,
|
||||
'student_class' => 'X TKJ 1',
|
||||
'violation_type_id' => $type->id,
|
||||
'points' => 5,
|
||||
'description' => 'Terlambat datang',
|
||||
'violation_date' => now()->toDateString(),
|
||||
]);
|
||||
|
||||
$this->actingAs($user, 'sanctum')
|
||||
->getJson("/api/v1/parent/students/{$student->id}/violations")
|
||||
->assertOk()
|
||||
->assertJsonPath('total_points', 5)
|
||||
->assertJsonCount(1, 'violations');
|
||||
}
|
||||
|
||||
public function test_link_second_child(): void
|
||||
{
|
||||
$this->makeStudent(['nisn' => '0099990001']);
|
||||
$this->makeStudent([
|
||||
'nisn' => '0099990002',
|
||||
'student_number' => 'TEST/002',
|
||||
'full_name' => 'SISWA KEDUA',
|
||||
'parent_phone' => '081234567890',
|
||||
]);
|
||||
|
||||
$token = $this->postJson('/api/v1/parent/register', [
|
||||
'nisn' => '0099990001',
|
||||
'parent_phone' => '081298765432',
|
||||
'name' => 'Wali Test',
|
||||
'password' => 'rahasia123',
|
||||
])->json('token');
|
||||
|
||||
$this->withToken($token)
|
||||
->postJson('/api/v1/parent/students/link', [
|
||||
'nisn' => '0099990002',
|
||||
'parent_phone' => '081234567890',
|
||||
])
|
||||
->assertStatus(201)
|
||||
->assertJsonPath('link_status', 'active');
|
||||
}
|
||||
|
||||
public function test_register_device(): void
|
||||
{
|
||||
$this->makeStudent(['nisn' => '0099990001']);
|
||||
$token = $this->postJson('/api/v1/parent/register', [
|
||||
'nisn' => '0099990001',
|
||||
'parent_phone' => '081298765432',
|
||||
'name' => 'Wali Test',
|
||||
'password' => 'rahasia123',
|
||||
])->json('token');
|
||||
|
||||
$this->withToken($token)
|
||||
->postJson('/api/v1/parent/devices', [
|
||||
'platform' => 'android',
|
||||
'fcm_token' => 'fcm-abc-123',
|
||||
'device_name' => 'Pixel 8',
|
||||
])
|
||||
->assertStatus(201);
|
||||
|
||||
$this->assertDatabaseHas('parent_devices', [
|
||||
'fcm_token' => 'fcm-abc-123',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user