get(route('admin.settings.index')); $response->assertRedirect(route('login')); }); test('guest cannot update system settings', function () { $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'Unauthorized', ]); $response->assertRedirect(route('login')); }); test('guest cannot update homepage settings', function () { $response = $this->put(route('admin.settings.update-homepage'), [ 'hero_image_key' => 'unauthorized/image.jpg', ]); $response->assertRedirect(route('login')); }); test('guest cannot update social media settings', function () { $response = $this->put(route('admin.settings.update-social-media'), [ 'instagram_url' => 'https://unauthorized.com', ]); $response->assertRedirect(route('login')); }); test('guest cannot update hr settings', function () { $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', ]); $response->assertRedirect(route('login')); }); /* |-------------------------------------------------------------------------- | INDEX PAGE |-------------------------------------------------------------------------- */ test('authenticated users can visit the settings page', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.settings.index')); $response->assertOk(); }); test('settings page renders with correct inertia component', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.settings.index')); $response->assertInertia(fn (Assert $page) => $page ->component('admin/settings/index') ->has('system') ->has('homepage') ->has('socialMedia') ->has('hr') ); }); test('settings page contains system data', function () { $user = User::factory()->create(); $this->actingAs($user); $system = app(SystemSettings::class); $response = $this->get(route('admin.settings.index')); $response->assertInertia(fn (Assert $page) => $page ->where('system.app_name', $system->app_name) ->where('system.email', $system->email) ->where('system.phone', $system->phone) ); }); test('settings page contains hr data', function () { $user = User::factory()->create(); $this->actingAs($user); $hr = app(HRSettings::class); $response = $this->get(route('admin.settings.index')); $response->assertInertia(fn (Assert $page) => $page ->where('hr.scheduled_check_in_time', $hr->scheduled_check_in_time) ->where('hr.scheduled_check_out_time', $hr->scheduled_check_out_time) ->where('hr.late_penalty_amount', $hr->late_penalty_amount) ->where('hr.absent_penalty_amount', $hr->absent_penalty_amount) ); }); /* |-------------------------------------------------------------------------- | UPDATE SYSTEM |-------------------------------------------------------------------------- */ test('user can update system settings with valid data', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST Collection Updated', 'about_app' => 'Tentang DST Collection', 'address' => 'Jl. Test No. 123', 'email' => 'test@dst.com', 'phone' => '081234567890', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.settings.index')); $settings = app(SystemSettings::class); expect($settings->app_name)->toBe('DST Collection Updated'); expect($settings->about_app)->toBe('Tentang DST Collection'); expect($settings->address)->toBe('Jl. Test No. 123'); expect($settings->email)->toBe('test@dst.com'); expect($settings->phone)->toBe('081234567890'); }); test('system app_name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => '', ]); $response->assertSessionHasErrors('app_name'); }); test('system email must be valid email format', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST', 'email' => 'not-an-email', ]); $response->assertSessionHasErrors('email'); }); test('system app_name must not exceed 255 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => str_repeat('a', 256), ]); $response->assertSessionHasErrors('app_name'); }); test('system about_app must not exceed 2000 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST', 'about_app' => str_repeat('a', 2001), ]); $response->assertSessionHasErrors('about_app'); }); test('system address must not exceed 500 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST', 'address' => str_repeat('a', 501), ]); $response->assertSessionHasErrors('address'); }); test('system phone must not exceed 20 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST', 'phone' => str_repeat('1', 21), ]); $response->assertSessionHasErrors('phone'); }); test('system nullable fields can be empty', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST', 'about_app' => '', 'address' => '', 'email' => '', 'phone' => '', ]); $response->assertSessionHasNoErrors(); }); /* |-------------------------------------------------------------------------- | UPDATE HOMEPAGE |-------------------------------------------------------------------------- */ test('user can update homepage settings with valid data', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-homepage'), [ 'hero_image_key' => 'homepage/hero/2026/07/31/abc123/hero.jpg', 'about_image_key' => 'homepage/about/2026/07/31/def456/about.jpg', 'gallery_image_keys' => ['homepage/gallery/2026/07/31/g1/img.jpg', 'homepage/gallery/2026/07/31/g2/img.jpg'], ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.settings.index')); $settings = app(HomepageSettings::class); expect($settings->hero_image_url)->toBe('homepage/hero/2026/07/31/abc123/hero.jpg'); expect($settings->about_image_url)->toBe('homepage/about/2026/07/31/def456/about.jpg'); expect($settings->gallery_images)->toBe(['homepage/gallery/2026/07/31/g1/img.jpg', 'homepage/gallery/2026/07/31/g2/img.jpg']); }); test('homepage fields are nullable', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-homepage'), [ 'hero_image_key' => null, 'about_image_key' => null, ]); $response->assertSessionHasNoErrors(); }); test('homepage hero_image_key must not exceed 2000 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-homepage'), [ 'hero_image_key' => str_repeat('a', 2001), ]); $response->assertSessionHasErrors('hero_image_key'); }); test('homepage gallery_image_keys must be array', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-homepage'), [ 'gallery_image_keys' => 'not-an-array', ]); $response->assertSessionHasErrors('gallery_image_keys'); }); test('homepage gallery_image_keys items must not exceed 2000 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-homepage'), [ 'gallery_image_keys' => [str_repeat('a', 2001)], ]); $response->assertSessionHasErrors('gallery_image_keys.0'); }); /* |-------------------------------------------------------------------------- | UPDATE SOCIAL MEDIA |-------------------------------------------------------------------------- */ test('user can update social media settings with valid data', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-social-media'), [ 'instagram_url' => 'https://instagram.com/dst', 'facebook_url' => 'https://facebook.com/dst', 'tiktok_url' => 'https://tiktok.com/@dst', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.settings.index')); $settings = app(SocialMediaSettings::class); expect($settings->instagram_url)->toBe('https://instagram.com/dst'); expect($settings->facebook_url)->toBe('https://facebook.com/dst'); expect($settings->tiktok_url)->toBe('https://tiktok.com/@dst'); }); test('social media fields are nullable', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-social-media'), [ 'instagram_url' => null, 'facebook_url' => null, 'tiktok_url' => null, ]); $response->assertSessionHasNoErrors(); }); test('social media instagram_url must not exceed 500 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-social-media'), [ 'instagram_url' => str_repeat('a', 501), ]); $response->assertSessionHasErrors('instagram_url'); }); test('social media facebook_url must not exceed 500 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-social-media'), [ 'facebook_url' => str_repeat('a', 501), ]); $response->assertSessionHasErrors('facebook_url'); }); test('social media tiktok_url must not exceed 500 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-social-media'), [ 'tiktok_url' => str_repeat('a', 501), ]); $response->assertSessionHasErrors('tiktok_url'); }); /* |-------------------------------------------------------------------------- | UPDATE HR |-------------------------------------------------------------------------- */ test('user can update hr settings with valid data', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', 'scheduled_check_out_time' => '18:00', 'late_penalty_amount' => 10000, 'absent_penalty_amount' => 50000, ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.settings.index')); $settings = app(HRSettings::class); expect($settings->scheduled_check_in_time)->toBe('09:00'); expect($settings->scheduled_check_out_time)->toBe('18:00'); expect($settings->late_penalty_amount)->toBe(10000); expect($settings->absent_penalty_amount)->toBe(50000); }); test('hr scheduled_check_in_time is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '', 'scheduled_check_out_time' => '18:00', 'late_penalty_amount' => 0, 'absent_penalty_amount' => 0, ]); $response->assertSessionHasErrors('scheduled_check_in_time'); }); test('hr scheduled_check_out_time is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', 'scheduled_check_out_time' => '', 'late_penalty_amount' => 0, 'absent_penalty_amount' => 0, ]); $response->assertSessionHasErrors('scheduled_check_out_time'); }); test('hr late_penalty_amount is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', 'scheduled_check_out_time' => '18:00', 'late_penalty_amount' => '', 'absent_penalty_amount' => 0, ]); $response->assertSessionHasErrors('late_penalty_amount'); }); test('hr absent_penalty_amount is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', 'scheduled_check_out_time' => '18:00', 'late_penalty_amount' => 0, 'absent_penalty_amount' => '', ]); $response->assertSessionHasErrors('absent_penalty_amount'); }); test('hr penalty amounts must be at least 0', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', 'scheduled_check_out_time' => '18:00', 'late_penalty_amount' => -1, 'absent_penalty_amount' => -1, ]); $response->assertSessionHasErrors(['late_penalty_amount', 'absent_penalty_amount']); }); test('hr penalty amounts must be integers', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:00', 'scheduled_check_out_time' => '18:00', 'late_penalty_amount' => 1000.50, 'absent_penalty_amount' => 2000.75, ]); $response->assertSessionHasErrors(['late_penalty_amount', 'absent_penalty_amount']); }); test('hr time fields must not exceed 5 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '09:000', 'scheduled_check_out_time' => '18:000', 'late_penalty_amount' => 0, 'absent_penalty_amount' => 0, ]); $response->assertSessionHasErrors(['scheduled_check_in_time', 'scheduled_check_out_time']); }); /* |-------------------------------------------------------------------------- | EDGE CASES & DATA INTEGRITY |-------------------------------------------------------------------------- */ test('system settings default values exist', function () { $settings = app(SystemSettings::class); expect($settings->app_name)->not->toBeEmpty(); expect($settings->about_app)->not->toBeNull(); expect($settings->address)->not->toBeNull(); expect($settings->email)->not->toBeNull(); expect($settings->phone)->not->toBeNull(); }); test('hr settings default values exist', function () { $settings = app(HRSettings::class); expect($settings->scheduled_check_in_time)->not->toBeEmpty(); expect($settings->scheduled_check_out_time)->not->toBeEmpty(); expect($settings->late_penalty_amount)->toBeInt(); expect($settings->absent_penalty_amount)->toBeInt(); }); test('updating one setting group does not affect others', function () { $user = User::factory()->create(); $this->actingAs($user); $systemBefore = app(SystemSettings::class); $hrBefore = app(HRSettings::class); $this->put(route('admin.settings.update-system'), [ 'app_name' => 'Changed Name', 'about_app' => $systemBefore->about_app, 'address' => $systemBefore->address, 'email' => $systemBefore->email, 'phone' => $systemBefore->phone, ]); $systemAfter = app(SystemSettings::class); $hrAfter = app(HRSettings::class); expect($systemAfter->app_name)->toBe('Changed Name'); expect($hrAfter->scheduled_check_in_time)->toBe($hrBefore->scheduled_check_in_time); expect($hrAfter->late_penalty_amount)->toBe($hrBefore->late_penalty_amount); }); /* |-------------------------------------------------------------------------- | REALISTIC USER SCENARIOS |-------------------------------------------------------------------------- */ test('user updates all settings groups in sequence', function () { $user = User::factory()->create(); $this->actingAs($user); // Update system $this->put(route('admin.settings.update-system'), [ 'app_name' => 'DST Updated', 'about_app' => 'About Updated', 'address' => 'Address Updated', 'email' => 'updated@dst.com', 'phone' => '089999999999', ])->assertSessionHasNoErrors(); // Update homepage $this->put(route('admin.settings.update-homepage'), [ 'hero_image_key' => 'homepage/hero/2026/07/31/new-hero.jpg', 'about_image_key' => 'homepage/about/2026/07/31/new-about.jpg', ])->assertSessionHasNoErrors(); // Update social media $this->put(route('admin.settings.update-social-media'), [ 'instagram_url' => 'https://instagram.com/newdst', 'facebook_url' => 'https://facebook.com/newdst', 'tiktok_url' => 'https://tiktok.com/@newdst', ])->assertSessionHasNoErrors(); // Update HR $this->put(route('admin.settings.update-hr'), [ 'scheduled_check_in_time' => '08:30', 'scheduled_check_out_time' => '17:30', 'late_penalty_amount' => 15000, 'absent_penalty_amount' => 75000, ])->assertSessionHasNoErrors(); // Verify all settings persisted $system = app(SystemSettings::class); $homepage = app(HomepageSettings::class); $socialMedia = app(SocialMediaSettings::class); $hr = app(HRSettings::class); expect($system->app_name)->toBe('DST Updated'); expect($homepage->hero_image_url)->toBe('homepage/hero/2026/07/31/new-hero.jpg'); expect($socialMedia->instagram_url)->toBe('https://instagram.com/newdst'); expect($hr->scheduled_check_in_time)->toBe('08:30'); expect($hr->late_penalty_amount)->toBe(15000); });