diff --git a/routes/web.php b/routes/web.php
index ad4fb9067..223d97c66 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -207,10 +207,6 @@ Route::middleware('auth')->group(function () {
Route::get('/', [HomeController::class, 'index']);
Route::get('/home', [HomeController::class, 'index']);
- // Settings
- Route::get('/settings', [SettingController::class, 'index'])->name('settings');
- Route::post('/settings', [SettingController::class, 'update']);
-
// Maintenance
Route::get('/settings/maintenance', [MaintenanceController::class, 'index']);
Route::delete('/settings/maintenance/cleanup-images', [MaintenanceController::class, 'cleanupImages']);
@@ -267,6 +263,11 @@ Route::middleware('auth')->group(function () {
Route::put('/settings/webhooks/{id}', [WebhookController::class, 'update']);
Route::get('/settings/webhooks/{id}/delete', [WebhookController::class, 'delete']);
Route::delete('/settings/webhooks/{id}', [WebhookController::class, 'destroy']);
+
+ // Settings
+ Route::redirect('/settings', '/settings/features')->name('settings');
+ Route::get('/settings/{category}', [SettingController::class, 'index']);
+ Route::post('/settings/{category}', [SettingController::class, 'update']);
});
// MFA routes
diff --git a/tests/Actions/WebhookFormatTesting.php b/tests/Actions/WebhookFormatTesting.php
new file mode 100644
index 000000000..4e9ba5e47
--- /dev/null
+++ b/tests/Actions/WebhookFormatTesting.php
@@ -0,0 +1,53 @@
+ Book::query()->first(),
+ ActivityType::CHAPTER_CREATE => Chapter::query()->first(),
+ ActivityType::PAGE_MOVE => Page::query()->first(),
+ ];
+
+ foreach ($events as $event => $entity) {
+ $data = $this->getWebhookData($event, $entity);
+
+ $this->assertEquals($entity->createdBy->name, Arr::get($data, 'related_item.created_by.name'));
+ $this->assertEquals($entity->updatedBy->id, Arr::get($data, 'related_item.updated_by.id'));
+ $this->assertEquals($entity->ownedBy->slug, Arr::get($data, 'related_item.owned_by.slug'));
+ }
+ }
+
+ public function test_page_create_and_update_events_show_revision_info()
+ {
+ /** @var Page $page */
+ $page = Page::query()->first();
+ $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
+
+ $data = $this->getWebhookData(ActivityType::PAGE_UPDATE, $page);
+ $this->assertEquals($page->currentRevision->id, Arr::get($data, 'related_item.current_revision.id'));
+ $this->assertEquals($page->currentRevision->type, Arr::get($data, 'related_item.current_revision.type'));
+ $this->assertEquals('Update a', Arr::get($data, 'related_item.current_revision.summary'));
+ }
+
+ protected function getWebhookData(string $event, $detail): array
+ {
+ $webhook = Webhook::factory()->make();
+ $user = $this->getEditor();
+ $formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time());
+
+ return $formatter->format();
+ }
+}
diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php
index fd953021d..0ab6d0e8c 100644
--- a/tests/Auth/AuthTest.php
+++ b/tests/Auth/AuthTest.php
@@ -202,7 +202,7 @@ class AuthTest extends TestCase
{
$this->assertFalse(setting('registration-role'));
- $resp = $this->asAdmin()->get('/settings');
+ $resp = $this->asAdmin()->get('/settings/registration');
$resp->assertElementContains('select[name="setting-registration-role"] option[value="0"][selected]', '-- None --');
}
diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php
index d00e8cf15..03ef926cb 100644
--- a/tests/Auth/LdapTest.php
+++ b/tests/Auth/LdapTest.php
@@ -348,6 +348,62 @@ class LdapTest extends TestCase
]);
}
+ public function test_dump_user_groups_shows_group_related_details_as_json()
+ {
+ app('config')->set([
+ 'services.ldap.user_to_groups' => true,
+ 'services.ldap.group_attribute' => 'memberOf',
+ 'services.ldap.remove_from_groups' => true,
+ 'services.ldap.dump_user_groups' => true,
+ ]);
+
+ $userResp = ['count' => 1, 0 => [
+ 'uid' => [$this->mockUser->name],
+ 'cn' => [$this->mockUser->name],
+ 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
+ 'mail' => [$this->mockUser->email],
+ ]];
+ $this->commonLdapMocks(1, 1, 4, 5, 4, 2);
+ $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
+ ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->andReturn($userResp, ['count' => 1,
+ 0 => [
+ 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
+ 'memberof' => [
+ 'count' => 1,
+ 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
+ ],
+ ],
+ ], [
+ 'count' => 1,
+ 0 => [
+ 'dn' => 'cn=ldaptester,ou=groups,dc=example,dc=com',
+ 'memberof' => [
+ 'count' => 1,
+ 0 => 'cn=monsters,ou=groups,dc=example,dc=com',
+ ],
+ ],
+ ], ['count' => 0]);
+
+ $resp = $this->mockUserLogin();
+ $resp->assertJson([
+ 'details_from_ldap' => [
+ 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
+ 'memberof' => [
+ 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
+ 'count' => 1,
+ ],
+ ],
+ 'parsed_direct_user_groups' => [
+ 'ldaptester',
+ ],
+ 'parsed_recursive_user_groups' => [
+ 'ldaptester',
+ 'monsters',
+ ],
+ ]);
+ }
+
public function test_external_auth_id_visible_in_roles_page_when_ldap_active()
{
$role = Role::factory()->create(['display_name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']);
diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php
index fc15bb8f3..141f072f8 100644
--- a/tests/Entity/ExportTest.php
+++ b/tests/Entity/ExportTest.php
@@ -386,6 +386,18 @@ class ExportTest extends TestCase
$resp->assertSee("# Dogcat\n\n```JavaScript\nvar a = 'cat';\n```\n\nAnother line", false);
}
+ public function test_page_markdown_export_handles_tasklist_checkboxes()
+ {
+ $page = Page::query()->first()->forceFill([
+ 'markdown' => '',
+ 'html' => '
',
+ ]);
+ $page->save();
+
+ $resp = $this->asEditor()->get($page->getUrl('/export/markdown'));
+ $resp->assertSee("- [x] Item A\n- [ ] Item B", false);
+ }
+
public function test_chapter_markdown_export()
{
$chapter = Chapter::query()->first();
@@ -408,6 +420,25 @@ class ExportTest extends TestCase
$resp->assertSee('# ' . $page->name);
}
+ public function test_book_markdown_export_concats_immediate_pages_with_newlines()
+ {
+ /** @var Book $book */
+ $book = Book::query()->whereHas('pages')->first();
+
+ $this->asEditor()->get($book->getUrl('/create-page'));
+ $this->get($book->getUrl('/create-page'));
+
+ [$pageA, $pageB] = $book->pages()->where('chapter_id', '=', 0)->get();
+ $pageA->html = '
hello tester
';
+ $pageA->save();
+ $pageB->name = 'The second page in this test';
+ $pageB->save();
+
+ $resp = $this->get($book->getUrl('/export/markdown'));
+ $resp->assertDontSee('hello tester# The second page in this test');
+ $resp->assertSee("hello tester\n\n# The second page in this test");
+ }
+
public function test_export_option_only_visible_and_accessible_with_permission()
{
$book = Book::query()->whereHas('pages')->whereHas('chapters')->first();
diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php
index 2ed7d3b41..fc6678788 100644
--- a/tests/Entity/PageRevisionTest.php
+++ b/tests/Entity/PageRevisionTest.php
@@ -144,13 +144,14 @@ class PageRevisionTest extends TestCase
public function test_revision_deletion()
{
- $page = Page::first();
+ /** @var Page $page */
+ $page = Page::query()->first();
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
- $page = Page::find($page->id);
+ $page->refresh();
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
- $page = Page::find($page->id);
+ $page->refresh();
$beforeRevisionCount = $page->revisions->count();
// Delete the first revision
@@ -158,18 +159,17 @@ class PageRevisionTest extends TestCase
$resp = $this->asEditor()->delete($revision->getUrl('/delete/'));
$resp->assertRedirect($page->getUrl('/revisions'));
- $page = Page::find($page->id);
+ $page->refresh();
$afterRevisionCount = $page->revisions->count();
$this->assertTrue($beforeRevisionCount === ($afterRevisionCount + 1));
// Try to delete the latest revision
$beforeRevisionCount = $page->revisions->count();
- $currentRevision = $page->getCurrentRevision();
- $resp = $this->asEditor()->delete($currentRevision->getUrl('/delete/'));
+ $resp = $this->asEditor()->delete($page->currentRevision->getUrl('/delete/'));
$resp->assertRedirect($page->getUrl('/revisions'));
- $page = Page::find($page->id);
+ $page->refresh();
$afterRevisionCount = $page->revisions->count();
$this->assertTrue($beforeRevisionCount === $afterRevisionCount);
}
diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php
index f69b5603c..fe2139e59 100644
--- a/tests/Permissions/RolesTest.php
+++ b/tests/Permissions/RolesTest.php
@@ -27,7 +27,7 @@ class RolesTest extends TestCase
public function test_admin_can_see_settings()
{
- $this->asAdmin()->get('/settings')->assertSee('Settings');
+ $this->asAdmin()->get('/settings/features')->assertSee('Settings');
}
public function test_cannot_delete_admin_role()
@@ -58,7 +58,7 @@ class RolesTest extends TestCase
$testRoleUpdateName = 'An Super Updated role';
// Creation
- $resp = $this->asAdmin()->get('/settings');
+ $resp = $this->asAdmin()->get('/settings/features');
$resp->assertElementContains('a[href="' . url('/settings/roles') . '"]', 'Roles');
$resp = $this->get('/settings/roles');
@@ -247,13 +247,13 @@ class RolesTest extends TestCase
public function test_settings_manage_permission()
{
- $this->actingAs($this->user)->get('/settings')->assertRedirect('/');
+ $this->actingAs($this->user)->get('/settings/features')->assertRedirect('/');
$this->giveUserPermissions($this->user, ['settings-manage']);
- $this->get('/settings')->assertOk();
+ $this->get('/settings/features')->assertOk();
- $resp = $this->post('/settings', []);
- $resp->assertRedirect('/settings');
- $resp = $this->get('/settings');
+ $resp = $this->post('/settings/features', []);
+ $resp->assertRedirect('/settings/features');
+ $resp = $this->get('/settings/features');
$resp->assertSee('Settings saved');
}
@@ -762,7 +762,7 @@ class RolesTest extends TestCase
public function test_public_role_visible_in_default_role_setting()
{
- $this->asAdmin()->get('/settings')
+ $this->asAdmin()->get('/settings/registration')
->assertElementExists('[data-system-role-name="admin"]')
->assertElementExists('[data-system-role-name="public"]');
}
diff --git a/tests/SecurityHeaderTest.php b/tests/SecurityHeaderTest.php
index 1a0a6c9b3..d8ba5873f 100644
--- a/tests/SecurityHeaderTest.php
+++ b/tests/SecurityHeaderTest.php
@@ -130,7 +130,7 @@ class SecurityHeaderTest extends TestCase
{
config()->set([
'app.iframe_sources' => 'https://example.com',
- 'services.drawio' => 'https://diagrams.example.com/testing?cat=dog',
+ 'services.drawio' => 'https://diagrams.example.com/testing?cat=dog',
]);
$resp = $this->get('/');
diff --git a/tests/Settings/FooterLinksTest.php b/tests/Settings/FooterLinksTest.php
index f1b5d4294..4b822ba4c 100644
--- a/tests/Settings/FooterLinksTest.php
+++ b/tests/Settings/FooterLinksTest.php
@@ -8,13 +8,13 @@ class FooterLinksTest extends TestCase
{
public function test_saving_setting()
{
- $resp = $this->asAdmin()->post('/settings', [
+ $resp = $this->asAdmin()->post('/settings/customization', [
'setting-app-footer-links' => [
['label' => 'My custom link 1', 'url' => 'https://example.com/1'],
['label' => 'My custom link 2', 'url' => 'https://example.com/2'],
],
]);
- $resp->assertRedirect('/settings');
+ $resp->assertRedirect('/settings/customization');
$result = setting('app-footer-links');
$this->assertIsArray($result);
@@ -30,7 +30,7 @@ class FooterLinksTest extends TestCase
['label' => 'Another Link', 'url' => 'https://example.com/link-b'],
]]);
- $resp = $this->asAdmin()->get('/settings');
+ $resp = $this->asAdmin()->get('/settings/customization');
$resp->assertSee('value="My custom link"', false);
$resp->assertSee('value="Another Link"', false);
$resp->assertSee('value="https://example.com/link-a"', false);
diff --git a/tests/Settings/SettingsTest.php b/tests/Settings/SettingsTest.php
new file mode 100644
index 000000000..bef354dac
--- /dev/null
+++ b/tests/Settings/SettingsTest.php
@@ -0,0 +1,39 @@
+asAdmin()->get('/settings');
+
+ $resp->assertRedirect('/settings/features');
+ }
+
+ public function test_settings_category_links_work_as_expected()
+ {
+ $this->asAdmin();
+ $categories = [
+ 'features' => 'Features & Security',
+ 'customization' => 'Customization',
+ 'registration' => 'Registration',
+ ];
+
+ foreach ($categories as $category => $title) {
+ $resp = $this->get("/settings/{$category}");
+ $resp->assertElementContains('h1', $title);
+ $resp->assertElementExists("form[action$=\"/settings/{$category}\"]");
+ }
+ }
+
+ public function test_not_found_setting_category_throws_404()
+ {
+ $resp = $this->asAdmin()->get('/settings/biscuits');
+
+ $resp->assertStatus(404);
+ $resp->assertSee('Page Not Found');
+ }
+}
diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php
index 775be92fc..cad2369f8 100644
--- a/tests/ThemeTest.php
+++ b/tests/ThemeTest.php
@@ -186,7 +186,7 @@ class ThemeTest extends TestCase
dispatch((new DispatchWebhookJob($webhook, $event, $detail)));
- $this->assertCount(3, $args);
+ $this->assertCount(5, $args);
$this->assertEquals($event, $args[0]);
$this->assertEquals($webhook->id, $args[1]->id);
$this->assertEquals($detail->id, $args[2]->id);
diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php
index 32f79e9e0..01754d2de 100644
--- a/tests/Uploads/ImageTest.php
+++ b/tests/Uploads/ImageTest.php
@@ -314,8 +314,8 @@ class ImageTest extends TestCase
$galleryFile = $this->getTestImage('my-system-test-upload.png');
$expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-system-test-upload.png');
- $upload = $this->call('POST', '/settings', [], [], ['app_logo' => $galleryFile], []);
- $upload->assertRedirect('/settings');
+ $upload = $this->call('POST', '/settings/customization', [], [], ['app_logo' => $galleryFile], []);
+ $upload->assertRedirect('/settings/customization');
$this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: ' . $expectedPath);