getProperty('resolvedSettings'); $prop->setAccessible(true); $prop->setValue(null, null); Cache::flush(); }); function makeNotificationAdmin(): User { $role = Role::firstOrCreate(['name' => 'Developer', 'guard_name' => 'web']); $user = User::factory()->create(); $user->assignRole($role); foreach (['view notification center', 'manage notification center'] as $name) { $perm = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']); $user->givePermissionTo($perm); } return $user; } function makeNotificationViewer(): User { $user = User::factory()->create(); $perm = Permission::firstOrCreate(['name' => 'view notification center', 'guard_name' => 'web']); $user->givePermissionTo($perm); return $user; } function makeNotificationForUser(User $user): Notification { $role = Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']); $notification = Notification::create([ 'title' => 'Test Notice', 'message' => 'Hello', 'recipient' => 'all', 'type' => 'info', 'created_by' => $user->id, ]); // Attach pivot row so the user sees it \DB::table('system_notification_user')->insert([ 'notification_id' => $notification->id, 'user_id' => $user->id, 'read_at' => null, 'deleted_at' => null, ]); return $notification; } // ── Access control ──────────────────────────────────────────────────────────── test('guest cannot view notification center', function () { $this->get('/notification-center')->assertRedirect('/login'); }); test('user without permission is forbidden from notification center', function () { $user = User::factory()->create(); $this->actingAs($user)->get('/notification-center')->assertForbidden(); }); test('user with view permission can access notification center', function () { $user = makeNotificationViewer(); $this->actingAs($user)->get('/notification-center')->assertOk(); }); // ── Store (broadcast) ───────────────────────────────────────────────────────── test('viewer without manage permission cannot broadcast notifications', function () { $viewer = makeNotificationViewer(); Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']); $this->actingAs($viewer) ->postJson('/notification-center', [ 'title' => 'Test', 'message' => 'Msg', 'recipient' => 'User', 'type' => 'info', ]) ->assertForbidden(); }); test('admin can broadcast a notification', function () { $admin = makeNotificationAdmin(); Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']); $this->actingAs($admin) ->postJson('/notification-center', [ 'title' => 'Important Update', 'message' => 'Please read.', 'recipient' => 'User', 'type' => 'info', ]) ->assertOk() ->assertJsonPath('success', true); expect(Notification::where('title', 'Important Update')->count())->toBe(1); }); test('store rejects unknown notification type', function () { $admin = makeNotificationAdmin(); Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']); $this->actingAs($admin) ->postJson('/notification-center', [ 'title' => 'Bad Type', 'message' => 'Msg', 'recipient' => 'User', 'type' => 'xss-alert', ]) ->assertStatus(422); }); test('store requires title and message', function () { $admin = makeNotificationAdmin(); $this->actingAs($admin) ->postJson('/notification-center', ['type' => 'info']) ->assertStatus(422); }); // ── Mark as read ────────────────────────────────────────────────────────────── test('user can mark a notification as read', function () { $user = makeNotificationViewer(); $notification = makeNotificationForUser($user); $this->actingAs($user) ->patchJson(route('notification-center.read', $notification->id)) ->assertOk() ->assertJsonPath('success', true); }); test('guest cannot mark notifications as read', function () { $notification = Notification::create([ 'title' => 'X', 'message' => 'Y', 'recipient' => 'all', 'type' => 'info', ]); // JSON requests return 401 Unauthorized (not a redirect) when unauthenticated $this->patchJson(route('notification-center.read', $notification->id)) ->assertUnauthorized(); }); // ── Mark all as read ────────────────────────────────────────────────────────── test('user can mark all notifications as read', function () { $user = makeNotificationViewer(); $this->actingAs($user) ->patchJson(route('notification-center.read-all')) ->assertOk() ->assertJsonPath('success', true); }); // ── Personal delete ─────────────────────────────────────────────────────────── test('user can delete (hide) a notification from their view', function () { $user = makeNotificationViewer(); $notification = makeNotificationForUser($user); $this->actingAs($user) ->deleteJson(route('notification-center.destroy', $notification->id)) ->assertOk() ->assertJsonPath('success', true); }); // ── Feature flag ────────────────────────────────────────────────────────────── test('feature disabled blocks regular viewers but allows manage-global-settings users', function () { app(SystemConfigService::class)->update(['feature_notification_center' => false]); $viewer = makeNotificationViewer(); $this->actingAs($viewer) ->get('/notification-center') ->assertForbidden(); }); test('feature flag disabled still allows users with manage global settings', function () { app(SystemConfigService::class)->update(['feature_notification_center' => false]); $admin = makeNotificationViewer(); $perm = Permission::firstOrCreate(['name' => 'manage global settings', 'guard_name' => 'web']); $admin->givePermissionTo($perm); $this->actingAs($admin) ->get('/notification-center') ->assertOk(); }); // ── Recent notifications API ────────────────────────────────────────────────── test('recent notifications endpoint returns json with unread count', function () { $user = makeNotificationViewer(); $this->actingAs($user) ->getJson('/notification-center/api/recent') ->assertOk() ->assertJsonStructure(['success', 'unread_count', 'notifications', 'has_more']); }); test('recent notifications content is escaped', function () { $user = makeNotificationViewer(); $notification = Notification::create([ 'title' => '', 'message' => 'bold', 'recipient' => 'all', 'type' => 'info', 'created_by' => $user->id, ]); \DB::table('system_notification_user')->insert([ 'notification_id' => $notification->id, 'user_id' => $user->id, 'read_at' => null, 'deleted_at' => null, ]); $response = $this->actingAs($user) ->getJson('/notification-center/api/recent') ->json(); $titles = collect($response['notifications'])->pluck('title')->all(); // Title must be HTML-entity-escaped, not raw script tag foreach ($titles as $t) { expect($t)->not->toContain('