mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Notifications: Add phpunit test for notification sending
Covers core case scenarios, and check of notification content.
This commit is contained in:
parent
bc6e19b2a1
commit
565908ef52
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace BookStack\Activity\Controllers;
|
namespace BookStack\Activity\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\Models\Watch;
|
|
||||||
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
@ -15,7 +14,9 @@ class WatchController extends Controller
|
|||||||
{
|
{
|
||||||
public function update(Request $request)
|
public function update(Request $request)
|
||||||
{
|
{
|
||||||
// TODO - Require notification permission
|
$this->checkPermission('receive-notifications');
|
||||||
|
$this->preventGuestAccess();
|
||||||
|
|
||||||
$requestData = $this->validate($request, [
|
$requestData = $this->validate($request, [
|
||||||
'level' => ['required', 'string'],
|
'level' => ['required', 'string'],
|
||||||
]);
|
]);
|
||||||
|
@ -8,6 +8,7 @@ use BookStack\Activity\Models\Loggable;
|
|||||||
use BookStack\Activity\Notifications\Messages\CommentCreationNotification;
|
use BookStack\Activity\Notifications\Messages\CommentCreationNotification;
|
||||||
use BookStack\Activity\Tools\EntityWatchers;
|
use BookStack\Activity\Tools\EntityWatchers;
|
||||||
use BookStack\Activity\WatchLevels;
|
use BookStack\Activity\WatchLevels;
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Settings\UserNotificationPreferences;
|
use BookStack\Settings\UserNotificationPreferences;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
|
|
||||||
@ -20,15 +21,16 @@ class CommentCreationNotificationHandler extends BaseNotificationHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main watchers
|
// Main watchers
|
||||||
|
/** @var Page $page */
|
||||||
$page = $detail->entity;
|
$page = $detail->entity;
|
||||||
$watchers = new EntityWatchers($page, WatchLevels::COMMENTS);
|
$watchers = new EntityWatchers($page, WatchLevels::COMMENTS);
|
||||||
$watcherIds = $watchers->getWatcherUserIds();
|
$watcherIds = $watchers->getWatcherUserIds();
|
||||||
|
|
||||||
// Page owner if user preferences allow
|
// Page owner if user preferences allow
|
||||||
if (!$watchers->isUserIgnoring($detail->created_by) && $detail->createdBy) {
|
if (!$watchers->isUserIgnoring($page->owned_by) && $page->ownedBy) {
|
||||||
$userNotificationPrefs = new UserNotificationPreferences($detail->createdBy);
|
$userNotificationPrefs = new UserNotificationPreferences($page->ownedBy);
|
||||||
if ($userNotificationPrefs->notifyOnOwnPageComments()) {
|
if ($userNotificationPrefs->notifyOnOwnPageComments()) {
|
||||||
$watcherIds[] = $detail->created_by;
|
$watcherIds[] = $page->owned_by;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,16 @@ abstract class Controller extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent access for guest users beyond this point.
|
||||||
|
*/
|
||||||
|
protected function preventGuestAccess(): void
|
||||||
|
{
|
||||||
|
if (!signedInUser()) {
|
||||||
|
$this->showPermissionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the current user's permissions against an ownable item otherwise throw an exception.
|
* Check the current user's permissions against an ownable item otherwise throw an exception.
|
||||||
*/
|
*/
|
||||||
|
@ -62,6 +62,7 @@ class UserPreferencesController extends Controller
|
|||||||
public function showNotifications(PermissionApplicator $permissions)
|
public function showNotifications(PermissionApplicator $permissions)
|
||||||
{
|
{
|
||||||
$this->checkPermission('receive-notifications');
|
$this->checkPermission('receive-notifications');
|
||||||
|
$this->preventGuestAccess();
|
||||||
|
|
||||||
$preferences = (new UserNotificationPreferences(user()));
|
$preferences = (new UserNotificationPreferences(user()));
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ class UserPreferencesController extends Controller
|
|||||||
public function updateNotifications(Request $request)
|
public function updateNotifications(Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('receive-notifications');
|
$this->checkPermission('receive-notifications');
|
||||||
|
$this->preventGuestAccess();
|
||||||
$data = $this->validate($request, [
|
$data = $this->validate($request, [
|
||||||
'preferences' => ['required', 'array'],
|
'preferences' => ['required', 'array'],
|
||||||
'preferences.*' => ['required', 'string'],
|
'preferences.*' => ['required', 'string'],
|
||||||
|
@ -27,7 +27,7 @@ class DummyContentSeeder extends Seeder
|
|||||||
// Create an editor user
|
// Create an editor user
|
||||||
$editorUser = User::factory()->create();
|
$editorUser = User::factory()->create();
|
||||||
$editorRole = Role::getRole('editor');
|
$editorRole = Role::getRole('editor');
|
||||||
$additionalEditorPerms = ['receive-notifications'];
|
$additionalEditorPerms = ['receive-notifications', 'comment-create-all'];
|
||||||
$editorRole->permissions()->syncWithoutDetaching(RolePermission::whereIn('name', $additionalEditorPerms)->pluck('id'));
|
$editorRole->permissions()->syncWithoutDetaching(RolePermission::whereIn('name', $additionalEditorPerms)->pluck('id'));
|
||||||
$editorUser->attachRole($editorRole);
|
$editorUser->attachRole($editorRole);
|
||||||
|
|
||||||
|
@ -2,9 +2,14 @@
|
|||||||
|
|
||||||
namespace Tests\Activity;
|
namespace Tests\Activity;
|
||||||
|
|
||||||
|
use BookStack\Activity\Notifications\Messages\CommentCreationNotification;
|
||||||
|
use BookStack\Activity\Notifications\Messages\PageCreationNotification;
|
||||||
|
use BookStack\Activity\Notifications\Messages\PageUpdateNotification;
|
||||||
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
||||||
use BookStack\Activity\WatchLevels;
|
use BookStack\Activity\WatchLevels;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
|
use BookStack\Settings\UserNotificationPreferences;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class WatchTest extends TestCase
|
class WatchTest extends TestCase
|
||||||
@ -83,6 +88,22 @@ class WatchTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_watch_update_fails_for_guest()
|
||||||
|
{
|
||||||
|
$this->setSettings(['app-public' => 'true']);
|
||||||
|
$guest = $this->users->guest();
|
||||||
|
$this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
|
||||||
|
$book = $this->entities->book();
|
||||||
|
|
||||||
|
$resp = $this->put('/watching/update', [
|
||||||
|
'type' => get_class($book),
|
||||||
|
'id' => $book->id,
|
||||||
|
'level' => 'comments'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertPermissionError($resp);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_watch_detail_display_reflects_state()
|
public function test_watch_detail_display_reflects_state()
|
||||||
{
|
{
|
||||||
$editor = $this->users->editor();
|
$editor = $this->users->editor();
|
||||||
@ -147,6 +168,147 @@ class WatchTest extends TestCase
|
|||||||
$respHtml->assertElementNotExists('form[action$="/watching/update"] button[name="level"][value="new"]');
|
$respHtml->assertElementNotExists('form[action$="/watching/update"] button[name="level"][value="new"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Guest user cannot see/set notifications
|
public function test_notify_own_page_changes()
|
||||||
// TODO - Actual notification testing
|
{
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$prefs = new UserNotificationPreferences($editor);
|
||||||
|
$prefs->updateFromSettingsArray(['own-page-changes' => 'true']);
|
||||||
|
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
|
||||||
|
$this->asAdmin();
|
||||||
|
$this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']);
|
||||||
|
$notifications->assertSentTo($editor, PageUpdateNotification::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_notify_own_page_comments()
|
||||||
|
{
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$prefs = new UserNotificationPreferences($editor);
|
||||||
|
$prefs->updateFromSettingsArray(['own-page-comments' => 'true']);
|
||||||
|
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
|
||||||
|
$this->asAdmin()->post("/comment/{$entities['page']->id}", [
|
||||||
|
'text' => 'My new comment'
|
||||||
|
]);
|
||||||
|
$notifications->assertSentTo($editor, CommentCreationNotification::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_notify_comment_replies()
|
||||||
|
{
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$prefs = new UserNotificationPreferences($editor);
|
||||||
|
$prefs->updateFromSettingsArray(['comment-replies' => 'true']);
|
||||||
|
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
|
||||||
|
$this->actingAs($editor)->post("/comment/{$entities['page']->id}", [
|
||||||
|
'text' => 'My new comment'
|
||||||
|
]);
|
||||||
|
$comment = $entities['page']->comments()->first();
|
||||||
|
|
||||||
|
$this->asAdmin()->post("/comment/{$entities['page']->id}", [
|
||||||
|
'text' => 'My new comment response',
|
||||||
|
'parent_id' => $comment->id,
|
||||||
|
]);
|
||||||
|
$notifications->assertSentTo($editor, CommentCreationNotification::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_notify_watch_parent_book_ignore()
|
||||||
|
{
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$watches = new UserEntityWatchOptions($editor, $entities['book']);
|
||||||
|
$prefs = new UserNotificationPreferences($editor);
|
||||||
|
$watches->updateWatchLevel('ignore');
|
||||||
|
$prefs->updateFromSettingsArray(['own-page-changes' => 'true', 'own-page-comments' => true]);
|
||||||
|
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
|
||||||
|
$this->asAdmin()->post("/comment/{$entities['page']->id}", [
|
||||||
|
'text' => 'My new comment response',
|
||||||
|
]);
|
||||||
|
$this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']);
|
||||||
|
$notifications->assertNothingSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_notify_watch_parent_book_comments()
|
||||||
|
{
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$admin = $this->users->admin();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$watches = new UserEntityWatchOptions($editor, $entities['book']);
|
||||||
|
$watches->updateWatchLevel('comments');
|
||||||
|
|
||||||
|
// Comment post
|
||||||
|
$this->actingAs($admin)->post("/comment/{$entities['page']->id}", [
|
||||||
|
'text' => 'My new comment response',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notifications->assertSentTo($editor, function (CommentCreationNotification $notification) use ($editor, $admin, $entities) {
|
||||||
|
$mail = $notification->toMail($editor);
|
||||||
|
$mailContent = html_entity_decode(strip_tags($mail->render()));
|
||||||
|
return $mail->subject === 'New comment on page: ' . $entities['page']->getShortName()
|
||||||
|
&& str_contains($mailContent, 'View Comment')
|
||||||
|
&& str_contains($mailContent, 'Page Name: ' . $entities['page']->name)
|
||||||
|
&& str_contains($mailContent, 'Commenter: ' . $admin->name)
|
||||||
|
&& str_contains($mailContent, 'Comment: My new comment response');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_notify_watch_parent_book_updates()
|
||||||
|
{
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$admin = $this->users->admin();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$watches = new UserEntityWatchOptions($editor, $entities['book']);
|
||||||
|
$watches->updateWatchLevel('updates');
|
||||||
|
|
||||||
|
$this->actingAs($admin);
|
||||||
|
$this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']);
|
||||||
|
|
||||||
|
$notifications->assertSentTo($editor, function (PageUpdateNotification $notification) use ($editor, $admin) {
|
||||||
|
$mail = $notification->toMail($editor);
|
||||||
|
$mailContent = html_entity_decode(strip_tags($mail->render()));
|
||||||
|
return $mail->subject === 'Updated page: Updated page'
|
||||||
|
&& str_contains($mailContent, 'View Page')
|
||||||
|
&& str_contains($mailContent, 'Page Name: Updated page')
|
||||||
|
&& str_contains($mailContent, 'Updated By: ' . $admin->name)
|
||||||
|
&& str_contains($mailContent, 'you won\'t be sent notifications for further edits to this page by the same editor');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test debounce
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
$this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']);
|
||||||
|
$notifications->assertNothingSentTo($editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_notify_watch_parent_book_new()
|
||||||
|
{
|
||||||
|
$notifications = Notification::fake();
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$admin = $this->users->admin();
|
||||||
|
$entities = $this->entities->createChainBelongingToUser($editor);
|
||||||
|
$watches = new UserEntityWatchOptions($editor, $entities['book']);
|
||||||
|
$watches->updateWatchLevel('new');
|
||||||
|
|
||||||
|
$this->actingAs($admin)->get($entities['chapter']->getUrl('/create-page'));
|
||||||
|
$page = $entities['chapter']->pages()->where('draft', '=', true)->first();
|
||||||
|
$this->post($page->getUrl(), ['name' => 'My new page', 'html' => 'My new page content']);
|
||||||
|
|
||||||
|
$notifications->assertSentTo($editor, function (PageCreationNotification $notification) use ($editor, $admin) {
|
||||||
|
$mail = $notification->toMail($editor);
|
||||||
|
$mailContent = html_entity_decode(strip_tags($mail->render()));
|
||||||
|
return $mail->subject === 'New page: My new page'
|
||||||
|
&& str_contains($mailContent, 'View Page')
|
||||||
|
&& str_contains($mailContent, 'Page Name: My new page')
|
||||||
|
&& str_contains($mailContent, 'Created By: ' . $admin->name);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,14 @@ class UserRoleProvider
|
|||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the system "guest" user.
|
||||||
|
*/
|
||||||
|
public function guest(): User
|
||||||
|
{
|
||||||
|
return User::where('system_name', '=', 'public')->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new fresh user without any relations.
|
* Create a new fresh user without any relations.
|
||||||
*/
|
*/
|
||||||
|
@ -121,6 +121,21 @@ class UserPreferencesTest extends TestCase
|
|||||||
$resp->assertDontSee('All Page Updates & Comments');
|
$resp->assertDontSee('All Page Updates & Comments');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_notification_preferences_not_accessible_to_guest()
|
||||||
|
{
|
||||||
|
$this->setSettings(['app-public' => 'true']);
|
||||||
|
$guest = $this->users->guest();
|
||||||
|
$this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
|
||||||
|
|
||||||
|
$resp = $this->get('/preferences/notifications');
|
||||||
|
$this->assertPermissionError($resp);
|
||||||
|
|
||||||
|
$resp = $this->put('/preferences/notifications', [
|
||||||
|
'preferences' => ['comment-replies' => 'true'],
|
||||||
|
]);
|
||||||
|
$this->assertPermissionError($resp);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_update_sort_preference()
|
public function test_update_sort_preference()
|
||||||
{
|
{
|
||||||
$editor = $this->users->editor();
|
$editor = $this->users->editor();
|
||||||
|
Loading…
Reference in New Issue
Block a user